# 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](https://www.apollographql.com/blog/the-basics-of-graphql-in-5-links). The official [GraphQL documentation](https://graphql.org/) is another good starting point.

## Endpoint

PlentyLabs' GraphQL endpoint is:

```
https://api.plentylabs.com/graphql
```

The published [schema.graphql](https://app.plentylabs.com/api/schema.graphql) describes everything an API key can use, and [operations.graphql](https://app.plentylabs.com/api/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](https://app.plentylabs.com/create) 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>`

```shell
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](https://plentylabs.com/developers/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](https://plentylabs.com/developers/rate-limiting). 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.

```json
{
  "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`:

| Code | Meaning |
| --- | --- |
| `BAD_REQUEST` | The request body is not a valid GraphQL request. |
| `BAD_USER_INPUT` | An argument was refused. |
| `CONFLICT` | The change conflicts with the object's current state. |
| `CREDIT_LIMIT_REACHED` | The key's creator reached the credit limit a workspace owner set for them. |
| `EXTERNAL_SERVICE_ERROR` | A service the operation depends on failed. |
| `FORBIDDEN` | The key cannot select a field, or cannot reach the object. |
| `GRAPHQL_VALIDATION_FAILED` | The document does not match the schema. |
| `INSUFFICIENT_CREDITS` | The workspace does not have enough credits. |
| `INTERNAL_SERVER_ERROR` | Something failed on our side. |
| `MFA_ENROLLMENT_REQUIRED` | The workspace requires two-factor authentication and the key's creator has not set it up. |
| `NOT_FOUND` | The object does not exist. |
| `PAYLOAD_TOO_LARGE` | The request body is larger than 25 MB. |
| `QUERY_TOO_COMPLEX` | The document is too deep or selects too many fields. See rate limiting. |
| `RATE_LIMITED` | Too many requests. See rate limiting. |
| `REQUEST_TIMEOUT` | The request ran longer than its time limit. |
| `TIMEOUT` | A step inside the operation timed out. |
| `UNAUTHENTICATED` | The 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](https://app.plentylabs.com/api/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:

```graphql
query GetWorkspace {
  apiCredentialWorkspace {
    id
    name
  }
}
```

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

```graphql
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:

```graphql
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:

```graphql
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:

```graphql
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.

```graphql
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](https://plentylabs.com/developers/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:

```graphql
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:

```graphql
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](mailto:support@plentylabs.com).
