GraphQL APIGetting started

Getting started

PlentyLabs' public API is built using GraphQL. It is the same API our own apps use. If you are new to GraphQL, Apollo has resources for beginners. The official GraphQL documentation is another good starting point.

Endpoint

PlentyLabs' GraphQL endpoint is:

https://api.plentylabs.com/graphql

The published schema.graphql describes everything an API key can use, and operations.graphql holds every published operation document, so you can browse the schema in your editor.

Authentication

The PlentyLabs API supports workspace API keys.

API keys

API keys are the way to access the API from your own code. Visit Settings, Developers, API keys in PlentyLabs to create and manage them. Only a workspace owner can create a key, and a key works only in its own workspace.

To authenticate your requests, pass the key as a bearer token with the header Authorization: Bearer <API_KEY>

curl https://api.plentylabs.com/graphql \
  -H "Authorization: Bearer $PLENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": "query GetWorkspace { apiCredentialWorkspace { id name } }" }'

See Authentication for access levels, expiry and revocation.

Error handling

PlentyLabs' GraphQL API follows the standard GraphQL error format, returning errors within an errors array in the response. Each error object includes a message describing what went wrong, a path array indicating where in the query the error occurred, and extensions with a code you can branch on.

Always check for the errors array before assuming success. GraphQL queries can partially succeed with a 200 HTTP status, returning some data while including errors for failed fields. Monitor HTTP status codes for server errors (5xx) and make sure to correctly handle rate limits. Use strongly typed GraphQL clients when possible to catch validation errors before a request is sent, and always validate required fields to avoid runtime null value errors.

{
  "data": { "board": null },
  "errors": [
    {
      "message": "Board not found",
      "path": ["board"],
      "extensions": { "code": "NOT_FOUND" }
    }
  ]
}

Mutations can also return errors as data. Every mutation payload has an errors list of typed MutationError objects, such as a RateLimitedError with retryAfterSeconds. Select it when you want to handle a failure without parsing the top-level errors.

These are the error codes the API returns in extensions.code:

CodeMeaning
BAD_REQUESTThe request body is not a valid GraphQL request.
BAD_USER_INPUTAn argument was refused.
CONFLICTThe change conflicts with the object's current state.
CREDIT_LIMIT_REACHEDThe key's creator reached the credit limit a workspace owner set for them.
EXTERNAL_SERVICE_ERRORA service the operation depends on failed.
FORBIDDENThe key cannot select a field, or cannot reach the object.
GRAPHQL_VALIDATION_FAILEDThe document does not match the schema.
INSUFFICIENT_CREDITSThe workspace does not have enough credits.
INTERNAL_SERVER_ERRORSomething failed on our side.
MFA_ENROLLMENT_REQUIREDThe workspace requires two-factor authentication and the key's creator has not set it up.
NOT_FOUNDThe object does not exist.
PAYLOAD_TOO_LARGEThe request body is larger than 25 MB.
QUERY_TOO_COMPLEXThe document is too deep or selects too many fields. See rate limiting.
RATE_LIMITEDToo many requests. See rate limiting.
REQUEST_TIMEOUTThe request ran longer than its time limit.
TIMEOUTA step inside the operation timed out.
UNAUTHENTICATEDThe key is missing, expired or revoked.

Getting started

We recommend using a GraphQL client to explore the schema. Our API is explorable and queryable in the Playground, signed in to your workspace: pick an operation to see its document and variables, and run it against your own data. No download required.

Once you have your client set up, you can start making queries (read) and mutations (write) to the API.

Queries & mutations

To get information about the workspace your key belongs to, you can use the apiCredentialWorkspace query:

query GetWorkspace {
  apiCredentialWorkspace {
    id
    name
  }
}

As boards (and most other objects) are workspace based, every list query takes the workspaceId from that result:

query ListBoards($workspaceId: ID!) {
  boards(workspaceId: $workspaceId, first: 10) {
    edges {
      node {
        id
        title
        assetCount
      }
    }
  }
}

Once you have found the right board, you can get its pages and the cards on each page. Cards live on pages, so card operations take a page id, not the board id. Let's make a request with some other card metadata:

query GetBoard($id: ID!) {
  board(id: $id) {
    id
    title
    pages(first: 10) {
      edges {
        node {
          id
          title
          cards(first: 50) {
            edges {
              node {
                id
                assetType
                assetId
                positionX
                positionY
                width
                height
              }
            }
          }
        }
      }
    }
  }
}

We can also get any object by id with the node query, whichever type it is:

query GetNode($id: ID!) {
  node(id: $id) {
    __typename
    id
    ... on Board {
      title
    }
    ... on Image {
      status
      downloadUrl
    }
  }
}

Creating boards & generating images

To create a new board, use a mutation:

mutation CreateBoard($workspaceId: ID!) {
  createBoard(input: { workspaceId: $workspaceId, title: "Spring campaign" }) {
    success
    board {
      id
      title
      pages(first: 1) {
        edges {
          node {
            id
          }
        }
      }
    }
    errors {
      message
    }
  }
}

This mutation will create a new board and return its id and title if the call was successful (success: true). A new board comes with one page; keep that page's id for placing cards.

A common use case after creating a board is generating an image onto it. To do this we use the generateImage mutation. Generation returns at once, with each image in status QUEUED; credits are charged when the mutation is accepted and refunded if the generation fails.

mutation GenerateImage($workspaceId: ID!) {
  generateImage(
    workspaceId: $workspaceId
    input: { prompt: "A ceramic mug on a linen table", aspectRatio: "4:5", count: 1 }
  ) {
    success
    images {
      id
      status
    }
    errors {
      message
    }
  }
}

Poll asset(id) until status is COMPLETED or FAILED, then place the image on the page with createCanvasCards. Generating an asset walks through the whole flow, including video and audio.

Accessing media

url, videoUrl and thumbnailUrl load only inside the PlentyLabs app. To show or fetch media anywhere else, use the signed fields. signedUrl on images and audio, signedVideoUrl and signedThumbnailUrl on videos, and signedThumbnailUrl on artboards load from any origin. downloadUrl and thumbnailDownloadUrl download the file. Each link is valid for one hour; select the field again for a fresh one.

signedUrl on images and signedThumbnailUrl take max, the longest side in pixels up to 4096, and format to return a resized copy.

Other examples

Queries

There are many ways to fetch assets. One common use case is to get the videos on one board:

query BoardVideos($workspaceId: ID!, $boardId: ID!) {
  assets(workspaceId: $workspaceId, first: 20, filter: { types: [VIDEO], boardIds: [$boardId] }) {
    edges {
      node {
        id
        displayName
        ... on Video {
          status
          duration
          downloadUrl
        }
      }
    }
  }
}

Searching the library works the same way, with the search argument:

query SearchAssets($workspaceId: ID!) {
  assets(workspaceId: $workspaceId, first: 20, search: "mug") {
    edges {
      node {
        __typename
        id
        displayName
      }
    }
  }
}

Processing assets

Assets that are still being generated are hidden by default from assets. They can be included by passing includeProcessing: true in the filter.

Support

If you run into problems or have questions or suggestions, send us a note at support@plentylabs.com.