Skip to main content

Syncing Customers to Prismatic

As a B2B software company you likely rely on a customer or user management system like Salesforce or some in-house system to keep track of your customers. In this tutorial, we'll cover how to sync customers from an external system to Prismatic using Prismatic's GraphQL API. We'll highlight External IDs, which are a way to assign a unique external identifier to a customer.

Setting Up#

We'll use Python in this tutorial, though the same ideas can be adapted to any language that has a GraphQL client library. Full code shown in this tutorial can be found on GitHub.

To start, we'll add gql to our dependencies for our Python project:

pip install gql

Next, we'll import our necessary libraries and create a GraphQL client so we can run queries against Prismatic's API. We'll define the transport layer for getting to our API (HTTP), which will include an authorization header to authenticate our client against Prismatic:

import jsonimport osimport sysfrom gql import gql, Clientfrom gql.transport.requests import RequestsHTTPTransport
token = os.environ['PRISMATIC_API_KEY']api_endpoint = "https://app.prismatic.io/api/"
transport = RequestsHTTPTransport(  url=api_endpoint,  headers={'Authorization': f'Bearer {token}'})client = Client(transport=transport)

This code assumes that an environment variable, PRISMATIC_API_KEY, has been set (it's best practice not to hard-code API keys). To get an API key into our environment variables, we can leverage the me:token prism subcommand:

export PRISMATIC_API_KEY=$(prism me:token)

Now that we have a working client, let's write some GraphQL queries and mutations to manage customers within Prismatic:

GraphQL Queries and Mutations#

To sync customers, we'll use the customers query and createCustomer and deleteCustomer mutations.

GraphQL Queries and Mutations

In GraphQL lingo, a query is similar to a SELECT statement in SQL. You query for a particular set of information in a read-only way.

A mutation is similar to an INSERT, UPDATE, or DELETE statement in SQL. You mutate data by creating new records, or updating or deleting existing records.

List All Customers#

First, let's write a function that gets a list of all of our customers, including their name, description, Prismatic id, and externalId:

def getCustomers():  query = gql("""    query {      customers (isSystem: false) {        nodes {          id          name          description          externalId        }      }    }  """)  result = client.execute(query)  return result['customers']['nodes']

This function runs a customers query against Prismatic's API. We specify isSystem: false because your Prismatic tenant has a set of "system" customers that are used behind-the-scenes for running test integrations as you build them. This will filter out those "system" customers, and return only your "real" customers.

The query returns an object with a customers key, which has (in GraphQL lingo) has a series of nodes (customers). We return result['customers']['nodes'] so that just a list of customer objects are returned.

Let's invoke this function, and use json.dumps() for readability:

print(json.dumps(getCustomers()))
[  {    "id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4",    "name": "Mars Missions",    "description": "Mars Missions Corp",    "externalId": "abc-123"  },  {    "id": "Q3VzdG9tZXI6MDllZDQyNTctMTNkMS00YTY4LWFiNTktY2Y5NzNmZGUyOTQy",    "name": "Eastern Space Flight",    "description": "Eastern Space Flight - Houston, TX",    "externalId": "xyz-456"  }]

We could use the output of this function to figure out which customers have not been synced into Prismatic, or to sync Prismatic customers back to an outside system.

GraphQL Pagination#

Before moving on, I want to address pagination. By default, the Prismatic API returns the first 100 results for a query. So, the customers query only returns 100 customers, even if we have more than 100 in the system.

If we want to download all customers, we'll need to update our getCustomers() function a bit. We'll update our query so that it requests pagination information (pageInfo). We'll request whether or not there are additional pages to fetch (hasNextPage), and a unique ID indicating where to start the next page (endCursor). We'll feed that endCursor back in to our query as a parameter after, so the GraphQL API knows where it last left off:

def getCustomers():  query = gql("""    query ($startCursor: String){      customers(after: $startCursor, isSystem: false) {        nodes {          id          name          description          externalId        }        pageInfo {          hasNextPage          endCursor        }      }    }  """)
  cursor = ""  hasNextPage = True  customers = [] # Used to accumulate customer objects
  while hasNextPage:    result = client.execute(query, variable_values={"startCursor": cursor})    customers += result['customers']['nodes']    hasNextPage = result['customers']['pageInfo']['hasNextPage']    cursor = result['customers']['pageInfo']['endCursor']
  return customers

Fetch a Specific Customer by External ID#

Now, let's look at how to look up a specific customer by their externalId. The customers query allows you to filter customers by externalId. We will pass in the externalId as a variable to the customers query using an object, params, that contains the GraphQL variables we want to use. We'll also assert that we got exactly one customer object back from the API:

def getCustomerByExternalId(externalId):  query = gql("""    query ($externalId: String!) {      customers (externalId: $externalId) {        nodes {          id          name          description          externalId        }      }    }  """)  params = {"externalId": externalId}  response = client.execute(query, variable_values=params)  assert len(response["customers"]["nodes"]) == 1, f"No customer with external ID '{externalId}' exists."  return response["customers"]["nodes"][0]

If we invoke this function now, we'll get a single customer based on the externalId that we provide:

print(json.dumps(getCustomerByExternalId("abc-123")))
{  "id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4",  "name": "Mars Missions",  "description": "Mars Missions Corp",  "externalId": "abc-123"}

Create a New Customer#

Next, we'll write a function that creates a new customer given the customer's name, description, and externalId. To do that, we'll use the the createCustomer GraphQL mutation. Similar to the getCustomerByExternalId function above, we'll supply some input variables to our mutation with a params object:

def createCustomer(name, description="", externalId=""):  mutation = gql("""    mutation($name: String!, $description: String, $externalId: String) {      createCustomer(        input: { name: $name, description: $description, externalId: $externalId }      ) {        customer {          id          name          description          externalId        }        errors {          messages        }      }    }  """)
  params = {    "name": name,    "description": description,    "externalId": externalId  }
  result = client.execute(mutation, variable_values=params)
  if ("errors" in result):    raise Exception(result.errors)  else:    return result["createCustomer"]["customer"]

The createCustomer mutation will return errors if the name or externalId you specified is already in the Prismatic system. If the mutation does not throw an error, it will return a customer object containing the new customer's name, description, externalId and generated Prismatic id. Let's try it out:

print(  json.dumps(    createCustomer(      name="Rockets Rockets Rockets",      description="Rockets^3",      externalId="456-xyz")))
{  "id": "Q3VzdG9tZXI6NGQ3ZDc3ZTktOTllNy00NmJiLWFlNDktMTg1N2JlNWNiYjUz",  "name": "Rockets Rockets Rockets",  "description": "Rockets^3",  "externalId": "456-xyz"}

Delete a Customer by External ID#

For the last customer-related function, let's delete a customer given their external ID. To do that, we'll use the deleteCustomer mutation. First, we'll look up the customer we would like to delete using the getCustomerByExternalId function we wrote previously. Then, we'll feed the Prismatic ID that we get into the deleteCustomer mutation:

def deleteCustomer(externalId):  customer = getCustomerByExternalId(externalId)  mutation = gql("""    mutation ($id: ID!) {      deleteCustomer(        input: {          id: $id        }      ) {        customer {          id          name          description          externalId        }        errors {          messages        }      }    }  """)
  params = {"id": customer["id"]}
  result = client.execute(mutation, variable_values=params)
  if ("errors" in result):    raise Exception(result.errors)  else:    return result["deleteCustomer"]["customer"]

If we supply an external ID that is not attached to a customer, the getCustomerByExternalId() function will throw an error indicating that.