Common API Queries and Mutations
As you incorporate Prismatic into your development ecosystem, it's handy to be able to wrap Prismatic's GraphQL-based API so you can programmatically manage users, customers, instances, monitors, alerting and more. On this page you will find examples of how to wrap common Prismatic GraphQL queries and and mutations.
This page doesn't cover every query or mutation that Prismatic supports, but should give you a good idea of how to do standard CRUD (create, read, update, delete) operations using the Prismatic API.
Before you begin, please read through our Intro to Prismatic's API article and generate an API token for yourself, so you can make API calls.
Managing Customers Through the API
Let's start by managing customers programmatically. If you'd like to see examples of these queries and mutations in code, check out this python script that wraps many of the customer-related queries and mutations.
Querying Customers Through the API
If you know the ID of the customer you would like to query, use the customer query.
The myCustomerId
(you can call it anything) query variable that you pass into the query is substituted in for customer(id)
, and a customer with the given ID is returned:
query ($myCustomerId: ID!) {
customer(id: $myCustomerId) {
id
name
externalId
}
}
{
"myCustomerId": "Q3VzdG9tZXI6ZDM0NTZjZWItNTNlOS00YmI5LWFhODItN2QyZDQ3YjZkMTA4"
}
While you can do some string concatenation or hard-code customer IDs, instance variable values, etc., to create a query, we recommend that you instead leverage GraphQL query variables. The examples below use query variables.
If you would like information about several customers, use the customers query instead:
query exampleGetCustomers {
customers(isSystem: false) {
nodes {
id
name
externalId
}
}
}
The isSystem: false
property above removes system-generated customers that are used when testing integrations in the integration designer.
Adding a New Customer Through the API
To add a new customer to your account, use the createCustomer mutation. (It's called a mutation and not a query because you are mutating data, not just accessing it). We'll make heave use of query variables for this mutation:
mutation ($customerName: String!, $customerDescription: String, $customerExternalId: String) {
createCustomer(
input: {name: $customerName, description: $customerDescription, externalId: $customerExternalId}
) {
customer {
id
}
errors {
field
messages
}
}
}
{
"customerName": "FTL Rockets",
"customerDescription": "Faster-than-light Rocket Company, LLC",
"customerExternalId": "abc-123"
}
In the createCustomer
example above, we capture the id
of the customer that is created, as well as errors
(if any) that are thrown.
The API might throw an error if a customer with the same name or external ID already exists.
Updating a Customer Through the API
You can update a customer using the updateCustomer mutation. You must know the ID of the customer you want to update. In this example, the ID of the customer and the new information that we want to change are passed in as query variables:
mutation ($customerId: ID!, $newDescription: String) {
updateCustomer(input: {id: $customerId, description: $newDescription}) {
errors {
field
messages
}
}
}
{
"customerId": "Q3VzdG9tZXI6ZDM0NTZjZWItNTNlOS00YmI5LWFhODItN2QyZDQ3YjZkMTA4",
"newDescription": "My Updated Description"
}
Deleting a Customer Through the API
The deleteCustomer mutation takes a customer ID, and returns the customer (if it has been deleted), or an error if the customer can't be found.
mutation ($customerId: ID!) {
deleteCustomer(input: {id: $customerId}) {
customer {
name
}
errors {
field
messages
}
}
}
{
"customerId": "Q3VzdG9tZXI6ZDM0NTZjZWItNTNlOS00YmI5LWFhODItN2QyZDQ3YjZkMTA4"
}
Managing Users Through the API
There are two types of users: your customer's users and your organization's team members. If you're using the embedded marketplace and signed JWTs to seamlessly authenticate customer users, you probably won't need to manage your customer users programmatically. It's more likely that you'll manage organization users, but we'll look at how to manage both.
Creating a User Through the API
In order to create a new user, you will need to know the user's email address, and role. You can optionally include the user's name and phone number.
First, look up the roles you can assign a user using either the customerRoles or organizationRoles query:
query {
organizationRoles {
id
name
permissions {
nodes {
name
description
}
}
}
}
This will give you a series of roles along with their IDs and the permissions that are associated with them. Find an appropriate role for the user you want to add, and note its ID. Next, run the either the createCustomerUser or createOrganizationUser mutation to create the user:
mutation ($email: String!, $name: String, $externalId: String, $role: ID!) {
createOrganizationUser(
input: {email: $email, name: $name, externalId: $externalId, role: $role}
) {
user {
id
name
email
externalId
}
errors {
field
messages
}
}
}
{
"email": "lisa.nguyen@smith-rockets.co",
"name": "Lisa Nguyen",
"externalId": "ABCCBC0B-98D0-453B-A795-0B430EFBF020",
"role": "Um9sZTo0NzdmNGRlYi03NzRlLTQ0M2UtOWY0MS01OGRmOWNhZjllNmM="
}
Users who are created in this way are immediately sent an email asking them to set a password for Prismatic.
Listing Users by Customer Through the API
One big advantage of GraphQL is that you can query resources for related data. Once again we'll use the customers query to list each customer's ID and name, but in addition we'll list the users attached to the customer. Specifically, we'll list each customer user's ID, email address, name, and external ID:
query {
customers(isSystem: false) {
nodes {
id
name
users {
nodes {
id
email
name
externalId
}
}
}
}
}
Updating and Deleting Users Through the API
You can accomplish the other CRUD operations (update and delete) using the updateUser and deleteUser mutations, similar to how you update or delete customers
Deploying New Instances Through the API
Find Integrations Through the API
Before deploying an instance of an integration, you'll need to know the ID of the version of the integration you want to deploy.
You can use the integrations query to list integrations.
You'll want to deploy a specific version of the integration, so including the allVersions: true
and versionIsAvailable: true
parameters will show you all available published versions of your integration (not just your canonical integration ID).
This query also uses sortBy
, so results are sorted by version, and the latest version is returned first.
query ($integrationName: String!) {
integrations(
name: $integrationName
allVersions: true
versionIsAvailable: true
sortBy: {direction: DESC, field: VERSION_NUMBER}
) {
nodes {
id
name
versionNumber
}
}
}
{
"integrationName": "Acme Inventory"
}
Creating a New Instance Through the API
The createInstance mutation requires four pieces of information:
- The ID of a customer this instance is for. This can be found through the customers query.
- The ID of the version of the integration you want to deploy. This can be found through above query.
- A name for your instance.
Once you've collected that information, you can create your instance:
mutation ($customerId: ID!, $integrationId: ID!, $name: String!) {
createInstance(
input: {customer: $customerId, integration: $integrationId, name: $name}
) {
instance {
id
name
flowConfigs {
nodes {
flow {
name
}
webhookUrl
}
}
}
errors {
field
messages
}
}
}
{
"customerId": "Q3VzdG9tZXI6OThiMjU3MDUtZmMzNC00NWYwLTk0ZDItODA0ZjFkYzEyYTZk",
"integrationId": "SW50ZWdyYXRpb246ZjY1Y2I5YTktMmZiZC00ZGE0LWIwYzktMjQ4Njc0YTY2NGMz",
"name": "Acme Inventory"
}
This query returns some additional data about the instance, including a list of flows and their respective webhook URLs for invoking the flows.
Config variables can be set during the createInstance
mutation.
They can also be set with an updateInstance
or updateInstanceConfigVariables
mutation, which we'll address below.
If you'd like to set the config variables as part of the createInstance
mutation, follow the same pattern that we show in the updateInstance
mutation below.
Updating Instance Config Variables Through the API
If you're programmatically updating an instance, you're likely updating the instance's config variable values. There are two mutations that you can use to update instance config variables:
-
updateInstance
lets you update multiple properties of an instance (like name, description, config variables, etc.). Note, though, that this mutation sets values for all config variables whether or not you specify a value for them. If you omit a config variable from the mutation, its value will be reset to its default value (ornull
if no default is configured). Use this mutation with caution.Setting config variables withupdateInstance
When you programmatically set config variables using the updateInstance mutation, all config variables must be given a value. If you omit a config variable, that config variable will be set to its default value (even if it was previously set).
If you'd like to set just a subset of config variables while leaving the others alone, use the updateInstanceConfigVariables mutation, described below.
-
updateInstanceConfigVariables
lets you safely update specific instance config variables, while leaving the other config variables untouched. Use this mutation if you would like to modify one (or a few) values.
Updating Config Variables with updateInstance
Before the updateInstance mutation, let's query the instance for its current config variables.
Each config variable is tied to a requiredConfigVariable
which has a key
(like Acme Inventory Endpoint
) and a defaultValue
.
Non-connection config variables also have a value
property, which is the customer-specific value you'd like to set for that config variable (like https://app.acme.com/api
).
If your config variable is a connection, which usually represents a username, password, API key, OAuth 2.0 connection, the shape is a bit different. It'll be comprised of a set of inputs
that have a name
, type
, and value
.
query ($instanceId: ID!) {
instance(id: $instanceId) {
name
configVariables {
nodes {
id
value
meta
requiredConfigVariable {
key
collectionType
dataType
defaultValue
}
inputs {
nodes {
name
type
value
}
}
}
}
}
}
{
"instanceId": "SW5zdGFuY2U6MWY0NmRmM2MtM2FjMi00ODMwLWExODEtZjNhN2FmN2RkNTRi"
}
If you run the query above, you'll get back something similar to the JSON below. Note that the different config variables each returned something slightly different, depending on the type of config variable they represent:
Acme Inventory Endpoint
is a plain string and returned avalue
of""
(empty string).Sync Inventory Item Metadata?
is a boolean toggle and returned avalue
of"false"
.Inventory Field Mapping
is a key-value list, and returned avalue
of"[]"
(empty array).Acme Inventory User/Pass
is a connection with twoinputs
-username
andpassword
which have blank values of their own.
{
"data": {
"instance": {
"name": "Acme Inventory",
"configVariables": {
"nodes": [
{
"id": "EXAMPLE",
"requiredConfigVariable": {
"key": "Inventory Field Mapping",
"collectionType": "KEYVALUELIST",
"dataType": "STRING",
"defaultValue": "[]"
},
"value": "[]",
"inputs": {
"nodes": []
}
},
{
"id": "EXAMPLE2",
"requiredConfigVariable": {
"key": "Sync Inventory Item Metadata?",
"collectionType": null,
"dataType": "BOOLEAN",
"defaultValue": "false"
},
"value": "false",
"inputs": {
"nodes": []
}
},
{
"id": "EXAMPLE3",
"requiredConfigVariable": {
"key": "Acme Inventory User/Pass",
"collectionType": null,
"dataType": "CONNECTION",
"defaultValue": null
},
"value": null,
"inputs": {
"nodes": [
{
"name": "password",
"type": "VALUE",
"value": ""
},
{
"name": "username",
"type": "VALUE",
"value": ""
}
]
}
},
{
"id": "EXAMPLE4",
"requiredConfigVariable": {
"key": "Acme Inventory Endpoint",
"collectionType": null,
"dataType": "STRING",
"defaultValue": ""
},
"value": "",
"inputs": {
"nodes": []
}
}
]
}
}
}
}
Once we know the shape of the config variables (which ones are strings, which ones are key-value lists, and which ones are connections, etc), we can send back a config variable object that matches that shape. We use values
to set connection config variables, and for connections and key-value lists we serialize the name-value or key-value pairs as JSON strings:
mutation ($instanceId: ID!, $configVariables: [InputInstanceConfigVariable]) {
updateInstance(input: {id: $instanceId, configVariables: $configVariables}) {
instance {
id
name
configVariables {
nodes {
requiredConfigVariable {
key
}
value
inputs {
nodes {
name
value
}
}
}
}
}
errors {
field
messages
}
}
}
{
"instanceId": "SW5zdGFuY2U6MWY0NmRmM2MtM2FjMi00ODMwLWExODEtZjNhN2FmN2RkNTRi",
"configVariables": [
{
"key": "Acme Inventory User/Pass",
"values": "[{\"name\":\"username\",\"type\":\"value\",\"value\":\"my.username\"},{\"name\":\"password\",\"type\":\"value\",\"value\":\"Pa$$W0Rd\"}]"
},
{
"key": "Acme Inventory Endpoint",
"value": "https://app.acme.com/api"
},
{
"key": "Sync Inventory Item Metadata?",
"value": "true"
},
{
"key": "Inventory Field Mapping",
"value": "[{\"key\":\"qty\",\"value\":\"Quantity\"},{\"key\":\"usd\",\"value\":\"Price\"}]"
}
]
}
Updating Config Variables with updateInstanceConfigVariables
This mutation lets you set values for a subset of config variables, while keeping the others untouched.
In this example, we set a string value for a config variable named My String
, a list of values for a list config variable named My List
, and a couple of values for a connection config variable named Amazon S3 Connection
which has a couple of connection inputs:
mutation ($instanceId: ID!, $configVariables: [InputInstanceConfigVariable]) {
updateInstanceConfigVariables(
input: {id: $instanceId, configVariables: $configVariables}
) {
instance {
id
}
errors {
field
messages
}
}
}
{
"instanceId": "SW5zdGFuY2U6MWY0NmRmM2MtM2FjMi00ODMwLWExODEtZjNhN2FmN2RkNTRi",
"configVariables": [
{
"key": "My String",
"value": "My Value"
},
{
"key": "My List",
"value": "[\"Value 1\",\"Value 2\"]"
},
{
"key": "Amazon S3 Connection",
"values": "[{\"name\":\"accessKeyId\",\"type\":\"value\",\"value\":\"MyAccessKey\"},{\"name\":\"secretAccessKey\",\"type\":\"value\",\"value\":\"MySecretAccessKey\"}]"
}
]
}
Importing an OAuth 2.0 Connection
If you are migrating to Prismatic from your own service or another integration platform, you may want to import your customers' OAuth 2.0 access and refresh tokens so that they do not need to re-authenticate.
To do that, you'll need to first deploy an instance of an integration.
Then, get an instance's config variables.
Identify the id
of the config variable that represents an OAuth 2.0 connection.
Using that config variable id
, along with the accessToken
and refreshToken
from the system you're migrating from you can import the OAuth 2.0 connection:
mutation (
$configVarId: ID!,
$accessToken: String!,
$refreshToken: String!,
$refreshAt: DateTime!,
$expiresIn: Int!
$additionalTokenFields: String
) {
updateOAuth2Connection(
input: {
id: $configVarId,
status: "active",
accessToken: $accessToken,
refreshToken: $refreshToken,
tokenType: "bearer",
refreshAt: $refreshAt,
expiresIn: $expiresIn,
additionalTokenFields: $additionalTokenFields
}
) {
instanceConfigVariable {
refreshAt
status
meta
}
errors {
field
messages
}
}
}
{
"configVarId": "SW5zdGFuY2VDb25maWdWYXJpYWJsZTpmMWI4ZGQzNi0yMzg0LTQ5MWUtOTE3ZC1iNTU1YjJiNzI2MmQ=",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiLwn5GNIFByaXNtYXRpYyIsIm5hbWUiOiLwn5GOIFRyYXkuaW8iLCJpYXQiOjE1MTYyMzkwMjJ9.xYUuI8BHov7C-E-ENa2Ox6z_L6StR1UjTuOqCaCnGTk",
"refreshToken": "super-secret-refresh-token",
"refreshAt": "2000-01-01T00:00:00.000Z",
"expiresIn": 60,
"additionalTokenFields": "{\"url\":\"https://example.com\",\"tenantId\":\"12345\"}"
}
Note: you can set refreshAt
to the UTC datetime when the token should next be refreshed, or you can set it to a date in the past (like the example above) to force an immediate refresh of the token.
User-level OAuth 2.0 connections can be imported simiarly using the updateUserLevelOAuth2Connection
.
Note that your config variable ID will start with VXN...
instead.
Deploying an Instance Through the API
An instance needs to be deployed in order for your new configuration to take effect. To deploy an instance, use the deployInstance mutation:
mutation ($instanceId: ID!) {
deployInstance(input: {id: $instanceId}) {
instance {
name
enabled
deployedVersion
lastDeployedAt
}
errors {
field
messages
}
}
}
{
"instanceId": "SW5zdGFuY2U6ODgyOTQwM2MtOGZkYS00ZGMwLTg3OGYtYWI5MWNhMmVmMTVj"
}
Fetching Step Results Through the API
When you invoke an integration asynchronously, an executionId
is returned:
curl https://hooks.prismatic.io/trigger/EXAMPLE== \
--data '{}' \
--header "Content-Type: application/json"
{"executionId":"SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6OTdiNWQxYmEtZGUyZi00ZDY4LWIyMTgtMDFlZGMwMTQxNTM5"}
You can use the executionResult along with that executionId
to fetch the results of that execution.
query ($executionId: ID!) {
executionResult(id: $executionId) {
startedAt
endedAt
error
stepResults (stepName: "codeBlock") {
nodes {
stepName
resultsUrl
}
}
}
}
{
"executionId": "SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6OTdiNWQxYmEtZGUyZi00ZDY4LWIyMTgtMDFlZGMwMTQxNTM5"
}
If the execution is still running, the endedAt
property will be null
.
You can query for stepResults
, and specifically ask for the resultsUrl
of a specific step.
Step names are "camelCased" in the stepResults
filter (so, My Code Block
-> myCodeBlock
).
Step results are written out to Amazon S3, and the resultsUrl
returns a short-lived presigned URL where you can fetch the step's results.
A step can return anything.
They often return javascript objects or JSON, but some steps return binary files like PDFs or MP3s or a JavaScript Buffer
.
To account for the variety of things that a step can return, Prismatic uses msgpack to package up step results.
The step result file you download from S3 will appear encoded, but after "unpacking" the contents, it'll look like normal JSON (or the type of data your step yielded).
To "unpack" step results, use msgpack.decode()
in NodeJS (or a similar function in a language of your choosing - msgpack
supports many languages).
Fetching and Unpacking Step Results with NodeJS
In the example below, assume that a step named Create Object
returned a JavaScript object with three properties: myImage1
, myImage2
, and myJavaScriptObject
.
The first two properties are images, and the third is a JavaScript object. This code will fetch the step results, decode them, and write the images and JavaScript object to disk. The JavaScript object will be encoded as JSON.
const msgpack = require("@msgpack/msgpack");
const fs = require("fs");
const PRISMATIC_URL =
process.env.PRISMATIC_URL || "https://app.prismatic.io/api";
const PRISMATIC_API_KEY = process.env.PRISMATIC_API_KEY;
const query = `query getStepResults ($executionId: ID!) {
executionResult(id: $executionId) {
startedAt
endedAt
error
stepResults(displayStepName: "Create Object") {
nodes {
stepName
resultsUrl
}
}
}
}`;
async function main() {
const executionId = process.argv[2];
// Make an API call to Prismatic to get the results URL
const apiResponse = await fetch(PRISMATIC_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${PRISMATIC_API_KEY}`,
},
body: JSON.stringify({
query,
variables: {
executionId,
},
}),
});
const apiResult = await apiResponse.json();
const resultUrl =
apiResult.data.executionResult.stepResults.nodes[0].resultsUrl;
// Get the result from Amazon S3
const encodedResult = await fetch(resultUrl);
const encodedResultBuffer = await encodedResult.arrayBuffer();
// Use msgpack to decode the results
const decodedResult = msgpack.decode(encodedResultBuffer);
const { myImage1, myImage2, myJavaScriptObject } = decodedResult.data;
// Write the results to disk
fs.writeFileSync("myImage1.png", myImage1.data);
fs.writeFileSync("myImage2.png", myImage2.data);
fs.writeFileSync(
"myJavaScriptObject.json",
JSON.stringify(myJavaScriptObject),
);
}
main();
Pagination with Prismatic's API
By default, most queries return a maximum of 100 results. If you have more than 100 records of a particular type (say, more than 100 instances), you can pull down 100, process those results, and then ask for the next 100 results, etc., until you process all records.
If you would like to fetch fewer than 100 records at a time, you can specify a first
property in your query.
To request page info, add pageInfo
alongside the nodes
of any query.
pageInfo
has several properties - the two you'll likely be interested in are hasNextPage
, which tells you if more results are available, and endCursor
, which is an indicator of where your query "left off", so your next query can pick up from there.
query getPageOfInstances($cursor: String) {
instances(
after: $cursor
isSystem: false
sortBy: [{field: CREATED_AT, direction: ASC}]
first: 10
) {
nodes {
id
name
customer {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
{
"cursor": "YXJyYXljb25uZWN0aW9uOjk5"
}
After fetching your results, you can check if pageInfo.hasNextPage
is true
.
If it is, grab the pageInfo.endCursor
(which might read something like YXJyYXljb25uZWN0aW9uOjEwMA==
) and run the same query, changing your query variables to read
{ "cursor": "YXJyYXljb25uZWN0aW9uOjEwMA==" }
That cursor
query variable will be used by the query above as the after
property.
That will cause the API to fetch up to 100 more results, but instead of starting at the beginning the API will start where your previous query left off.
When fetching multiple pages of results, sort order is important. Without a sort order, two consecutive pages may contain duplicate records, or you may miss records.
Most queries support a CREATED_AT
sort field, which will sort results by the time they were created.
In the query above, instances were sorted by creation date in ascending order with sortBy: [{field: CREATED_AT, direction: ASC}]
Some record types like logs
do not have a CREATED_AT
field, but have a TIMESTAMP
field that you can sort by instead.
components
are versioned, and versions of components can be sorted by VERSION_CREATED_AT
.
Other CRUD Operations Through the API
This article covers some common GraphQL queries and mutations that you might encounter, but there are dozens of other queries and mutations that are not covered here. For information on other queries and mutations that are available, see the sidebar to the left. Each query and mutation has its own documentation page with details on what inputs they expect, and what objects are returned.