> ## Documentation Index
> Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Protocol

> Learn how we request customer cards from your API and how to respond to these requests.

<Note>
  This page is intended for a technical audience that will be implementing a customer card API.

  Check out the [customer cards](/customer-cards) page for an overview of customer cards.
</Note>

Customer cards are not proactively loaded. They are just-in-time and pulled when required.
This means that if your APIs are slow then users of the Support App will see a loading spinner over the card.

The protocol is as follows:

1. When a user in Plain opens up a customer's page the cards are loaded.
2. Plain's backend figures out which cards can be returned from the cache and which cards need to be loaded. On the first
   load of the customer this would be all cards.
3. It calculates how many requests it needs to make (see [request deduplication](#request-deduplication) for
   details).
4. Your APIs are then called with the customer's details, so you can look up the customer's data in your systems
   (see [request](#request) section for details).
5. Your APIs then return customer cards that consist of [Plain UI components](/ui-components)
   (see [response](#response) section for details).
6. The cards are cached based on either an explicit TTL value in the response or the TTL in the card settings (see [caching](#caching)).
7. Cards are shown to the user in Plain.
8. Users can manually reload the card at any time in which case only that one card will be requested from your API.

A **few limits** to be aware of:

* Your API must **respond within 15 seconds**, or it will time out. See [retry strategy](#retry-strategy) for details on how timed-out requests are retried.
* You can configure a **maximum of 25 customer cards per workspace**.
* **Card keys must be unique within a workspace**. A key can only contain **alphanumeric**, **hyphen**, and **underscore** characters (regex: `[a-zA-Z0-9_-]+`).

## Request

Plain will make the following request to your backend:

* **Method**: `POST`
* **URL:** the URL you configured on customer cards settings page.
* **Headers:**
  * All the headers you provided on customer cards settings page. This should typically include authentication headers.
  * `Content-Type`: `application/json`
  * `Accept`: `application/json`
  * `Plain-Workspace-Id`: the ID of the workspace the customer is in. This is useful for logging or request routing.
  * `User-Agent`: `Plain/1.0 (plain.com; help@plain.com)`
  * `Plain-Request-Signature`: `XXX` (see [request signing](/request-signing) for details)
* **Body:**
  * `cardKeys`: an array of card keys being requested
  * `customer`: an object with the customer's core details
    * `id`: the id of the customer in Plain
    * `email`: the email of the customer
    * [`externalId`](https://www.plain.com/docs/graphql/customers/upsert) (optional): string if the customer has an `externalId`, otherwise it is `null`.
  * `thread` (optional): an object with the thread's details, if this customer card is being requested in the context of a thread
    * `id`: the id of the thread in Plain
    * `externalId` (optional): string if the thread has an `externalId`, otherwise it is `null`.

Example request body:

<Snippet file="customer-cards/customer-cards-request.mdx" />

### Request deduplication

If you configure multiple customer cards that have the same API details then Plain will batch them and make only one request.

The request deduplication logic for customer card configs is:

* The following config properties are ignored: Title, Card key, Default TTL
* **API URL:** Leading and trailing whitespaces are trimmed and then compared. **This is case sensitive**.
  * For example, these URLs would be considered **different**:
    * `https://api.example.com/cards`
    * `https://api.example.com/cards/`
    * `https://api.example.com/Cards`
* **API Headers:** Order of headers does not matter
  * **Header name:** Leading and trailing whitespaces are trimmed and then compared. **This is case insensitive**.
    * For example, these header names be considered **the same**:
      * `Authorization`
      * `AUTHORIZATION`
      * `   authorization   `
  * **Header value:** No processing done, compared as is (be careful with any extra whitespace characters)
    * For example, these header values would be considered **as different**:
      * `Bearer my-token`
      * `bearer my-token`
      * `   bearer my-token   `

## Response

<Warning>
  For each key requested a corresponding card **MUST** be returned in the response, otherwise an integration error will be returned for that card.

  Any extra cards in the response will be ignored.
</Warning>

Your API must respond with a **`200` status code** or the response body won't be processed and will be treated as an error.

The response body must be a JSON object with:

* `cards`: an array of cards. Every `cardKey` requested should have a corresponding `key` returned. Any extra returned
  cards will be ignored.
  * `key`: the requested key
  * `timeToLiveSeconds` (optional, nullable): can either be omitted or `null`. If provided it will override the default time to live value. This allows you to control caching on a case-by-case basis.
  * `components` (nullable): `null` to indicate that the card has no data or an array of [Plain UI Components](/ui-components/).

Example response body for a card cached for 1 hour:

<Snippet file="customer-cards/customer-cards-response.mdx" />

Example response body for a card that has no data and should not be displayed and TTL omitted:

<Snippet file="customer-cards/customer-cards-no-display-response.mdx" />

## Caching

We cache the responses we get from your APIs. This cache is controlled via two properties:

1. A time to live value (in seconds) in the customer card's settings. This can be changed under **Settings** → **Customer cards**. Any changes here will only apply to newly loaded customer cards.
2. An explicit time to live value (in seconds) in your API response with the key `timeToLiveSeconds`. This overrides the value from settings and allows your API to dynamically set the TTL using custom logic.

Any card that is past its expiry time will usually be deleted within a few minutes but no later than 48 hours after expiry.

## Retry strategy

Errors are classified into two categories:

1. **Retriable errors**: these are transient issues where retrying once is appropriate
2. **Integration errors**: these are typically programming or configuration errors. These errors won't be retried and cached for 5 minutes.

## Security

Plain supports [request signing](/request-signing) and [mTLS](/mtls) to verify that the request was made by Plain and not a third party.

### Retriable errors

The following errors are **retried once** after a **1-second delay**:

* HTTP `5xx` response status code
* HTTP `429` Too Many Requests response status code
* The request times out after 15 seconds.
* Plain fails to perform the request for some reason

Retriable errors are not cached, therefore if the cards are requested again via the Support App they will be re-requested.

### Integration errors

The following errors are **not retried**:

* All HTTP 4xx response status codes except for HTTP `429` Too Many Requests response status code
* A card key is missing in the response. For example, if `subscription-details` is requested but the `cards` array in the response doesn't have an element with the key `subscription-details`.
* The response body does not match the expected schema documented in [response](#response).

Integration errors are cached for 5 minutes and usually indicate a programming or configuration error.

Users can manually refresh a card in the UI, in which case the card will be requested again.
