# Cards SDK reference

> Every method in the Kustomer Cards SDK, for pages you embed with DynamicCard, widgets, modals, expanded insights views and custom settings pages.

Source: https://developer.kustomer.com/docs/apps/cards-sdk

The **Cards SDK** is the script that lets a page you host talk to Kustomer from inside an iframe. Load it in a page embedded with [`DynamicCard`](https://developer.kustomer.com/docs/apps/kviews#embed-your-own-ui), a [widget](https://developer.kustomer.com/docs/apps/widgets), a modal, an expanded insights view or a [custom settings page](https://developer.kustomer.com/docs/apps/settings#build-your-own-settings-page), and it gives you the record the agent is viewing, API access as the agent, and control over the card itself.

## Load the SDK

```html
<script src="https://cdn.kustomerapp.com/card-js/latest/kustomer-card.min.js"></script>
```

The script creates one `window.Kustomer` object. Kustomer opens your page with `parentUrl` and `id` query parameters, and the SDK uses them to send and accept `postMessage` events only from the Kustomer window that framed it. Messages from any other origin are ignored.

- Serve the page over HTTPS, and allow framing by Kustomer: no `X-Frame-Options: DENY`, and a `frame-ancestors` policy that includes your customers' Kustomer domains.
- Keep the `parentUrl` and `id` parameters if your page redirects. Without them the SDK logs a warning that it's running outside Kustomer and can't reach the agent desktop.
- Call [`Kustomer.initialize`](#kustomerinitialize) once on load. A card or widget that never initializes isn't shown.

## Context

The context describes what the agent is looking at. You receive it from `initialize` and every time it changes through [`Kustomer.on('context')`](#kustomeron). `context.type` is one of:

| `type`            | Where                                                                                                                                                        | Carries                                                                                   |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `customer`        | A card on a customer timeline with no item expanded                                                                                                          | `customer`                                                                                |
| `conversation`    | A card while the agent has a conversation open                                                                                                               | `customer`, `conversation`                                                                |
| `company`         | A card on a company                                                                                                                                          | `company`                                                                                 |
| `kobject.<klass>` | A card while a custom object is expanded, for example `kobject.acme_order`                                                                                   | `customer` and the object                                                                 |
| `widget`          | A [widget](https://developer.kustomer.com/docs/apps/widgets), anywhere but a customer's timeline. On a timeline, a widget gets that page's type and records. | `widget` (your app, with `isOpen`), `path` and `isWidget`, which every widget context has |
| `app`             | A [custom settings page](https://developer.kustomer.com/docs/apps/settings#build-your-own-settings-page)                                                     | The app                                                                                   |
| Your own          | A `DynamicCard` given a `context` prop                                                                                                                       | Your `customContext`                                                                      |

Records are full JSON:API resources, the same shape the REST API returns:

```json title="A conversation context (trimmed)"
{
  "type": "conversation",
  "customer": {
    "type": "customer",
    "id": "6650c1f2a8e3b40012ab34cd",
    "attributes": {
      "displayName": "Ada Lovelace",
      "emails": [{ "type": "home", "email": "ada@example.com" }],
      "conversationCounts": { "open": 1, "done": 3, "snoozed": 0, "all": 4 }
    },
    "relationships": { "org": { "data": { "type": "org", "id": "6650c1f2a8e3b40012ab0001" } } }
  },
  "conversation": {
    "type": "conversation",
    "id": "6650c1f2a8e3b40012ab56ef",
    "attributes": { "name": "Where is my order?", "channels": ["email"], "status": "open", "messageCount": 1 }
  }
}
```

To send your own data instead, pass a custom context to `DynamicCard`. Your page receives the object as is:

```jsx title="template"
<DynamicCard src="https://kustomer.acme.example/card" context={{ type: 'acmeOrder', customContext: { orderId: object.externalId } }} />
```

Context data can contain [masked fields](https://developer.kustomer.com/docs/apps/kviews#handle-masked-data). Read optional values defensively.

## Setup and events

### Kustomer.initialize

```ts
Kustomer.initialize(options?, callback?): Promise<[context, { userRoles }]>
```

Connects your page to Kustomer and delivers the first context. The callback receives `(context, { userRoles })`, and the returned promise resolves with the same two values as an array. Only the first call does anything; later calls resolve with `undefined`.

**options**

```js
Kustomer.initialize({ userRoles: ['org.admin.write'] }, (context, { userRoles }) => {
  render(context, { canEdit: userRoles['org.admin.write'] })
  Kustomer.resize()
})

// Or with the promise:
const [context, { userRoles }] = await Kustomer.initialize({ userRoles: ['org.admin.write'] })
```

Pass only a callback to skip the options: `Kustomer.initialize((context) => …)`.

### Kustomer.on

```ts
Kustomer.on(event: string, handler: (data, { userRoles }) => void): void
```

Subscribes to an event. Each event keeps one handler, so a second `on` for the same event replaces the first. An unknown event name is ignored, with a warning when `debug` is on.

| Event                            | Fires when                                                                                                                                                          | `data`                                                                                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `context`                        | The agent moves to another conversation, timeline item or record, or opens or closes a widget                                                                       | The new [context](#context)                                                                                                                   |
| `theme`                          | The agent's Kustomer theme changes, such as switching to dark mode                                                                                                  | The theme update. Before your handler runs, the SDK has already set the theme's CSS variables and `color-scheme` on your page's root element. |
| `flags`                          | The values of your `subscribedFlags` are sent or change                                                                                                             | An object of flag names to booleans                                                                                                           |
| `number-dialed`                  | The agent clicks a phone number in Kustomer to call it. Needs the `click-to-dial` [data subscription](https://developer.kustomer.com/docs/apps/widgets#voice-apps). | The number to dial, as a string                                                                                                               |
| `routing-status-change`          | The agent's routing status changes, such as available to busy. Needs `status-sync`.                                                                                 | The new status: `{ app, externalQueue, kustomerStatusId, externalStatusId, statusType }`                                                      |
| `kustomer-typing-status-changed` | The agent's typing status changes in Kustomer. Needs `typing-indicators`.                                                                                           | The typing status                                                                                                                             |

```js
Kustomer.on('context', (context) => {
  render(context)
  Kustomer.resize()
})
```

Theme updates also arrive with `initialize`, so pages that use the [theme variables](https://developer.kustomer.com/docs/apps/kviews#support-dark-mode) restyle without handling `theme` at all.

## Call APIs

### Kustomer.request

```ts
Kustomer.request({ method, url, body?, verboseResponse?, targetModal?, modalToken? }, callback?): Promise<data>
```

Calls the Kustomer REST API as the agent, with the agent's permissions. No API key is involved. The callback receives `(error, data)`. The promise resolves with the data, or rejects with an array whose first item is the error.

**options**

```js
async function closeConversation(context) {
  if (context.type !== 'conversation') return
  try {
    await Kustomer.request({ method: 'patch', url: `/v1/conversations/${context.conversation.id}`, body: { status: 'done' } })
    Kustomer.handleTriggerToast({ type: 'success', message: 'Conversation closed' })
  } catch ([err]) {
    Kustomer.updateStatus({ status: 'danger', description: 'Could not update the conversation.' })
  }
}
```

### Kustomer.command.run

```ts
Kustomer.command.run(name: string, body?: { args?, headers?, body? }, callback?): Promise<result>
```

Runs one of your app's [commands](https://developer.kustomer.com/docs/apps/kviews#call-apis-with-commands), so your page can call your own API with credentials that stay on Kustomer's servers. It's a `POST` to `/v1/commands/{name}/run` through `Kustomer.request`. The promise resolves with the response's `attributes`, which hold your API's response, and rejects like `request`. The callback receives `(error, result)`.

```js
const order = await Kustomer.command.run('acme_orders.app.lookup-order', {
  args: { storeDomain: 'shop.acme.example', orderId: '4417' },
})
```

## Card display

### Kustomer.resize

```ts
Kustomer.resize({ height?, width?, modal? }?): void
```

Resizes the iframe. With no `height`, the SDK measures your page and fits the card to it, so call `Kustomer.resize()` after every render that changes your page's height.

**options**

### Kustomer.updateStatus

```ts
Kustomer.updateStatus({ status: 'info' | 'warning' | 'danger', description: string }): void
```

Shows a colored banner on the card, for example when your API is unreachable. Any other `status` is ignored, with a warning when `debug` is on.

```js
Kustomer.updateStatus({ status: 'warning', description: 'Acme is slow to respond. Order data may be out of date.' })
```

### Kustomer.clearStatus

```ts
Kustomer.clearStatus(): void
```

Removes the banner shown by `updateStatus`.

### Kustomer.handleTriggerToast

```ts
Kustomer.handleTriggerToast({ type: 'info' | 'warning' | 'error' | 'success', message: string }): void
```

Shows a toast notification in Kustomer, to confirm or report something the agent did.

```js
Kustomer.handleTriggerToast({ type: 'success', message: 'Refunded order 4417' })
```

## Navigation and windows

### Kustomer.openCustomer

```ts
Kustomer.openCustomer(customerId: string): void
```

Opens a customer's timeline.

### Kustomer.openCustomerEvent

```ts
Kustomer.openCustomerEvent(customerId: string, eventId: string): void
```

Opens a customer's timeline at a specific item, such as a conversation.

```js
Kustomer.openCustomerEvent('6650c1f2a8e3b40012ab34cd', '6650c1f2a8e3b40012ab56ef')
```

### Kustomer.expandInsightsCard

```ts
Kustomer.expandInsightsCard({ appName, appLogo, title, titleExternalLink?, titleLinkText?, height, url, context }): void
```

Opens an expanded view that fills the whole insights panel, for detail that doesn't fit in a card, such as a full order. The view stays open while the agent moves between conversations and items on the same timeline, and closes on its own when the agent opens another customer.

**params**

It returns nothing and takes no callback. A callback you pass is never called, so don't wait on one.

```js title="card.js"
function showOrder(order) {
  Kustomer.expandInsightsCard({
    appName: 'Acme Orders',
    appLogo: 'https://kustomer.acme.example/logo.png',
    title: 'Order',
    titleExternalLink: `https://admin.acme.example/orders/${order.id}`,
    titleLinkText: `#${order.number}`,
    height: 600,
    url: 'https://kustomer.acme.example/order-detail',
    context: order,
  })
}
```

```js title="order-detail.js"
Kustomer.initialize((order) => {
  renderOrder(order)
})

document.querySelector('#done').addEventListener('click', () => Kustomer.contractInsightsCard())
```

### Kustomer.contractInsightsCard

```ts
Kustomer.contractInsightsCard(): void
```

Closes the expanded insights view. Like `expandInsightsCard`, it returns nothing and never calls a callback.

### Kustomer.open

```ts
Kustomer.open(): void
```

Opens your [widget](https://developer.kustomer.com/docs/apps/widgets), for example on an incoming call, or to bring the agent back when they close it with unsaved work.

### Kustomer.close

```ts
Kustomer.close(): void
```

Removes your iframe from the page, for example when a card has nothing to show for this customer.

```js
Kustomer.initialize((context) => {
  if (!context.customer?.attributes.emails?.length) return Kustomer.close()
  render(context)
  Kustomer.resize()
})
```

## Agent status

### Kustomer.requestUpdateUserStatus

```ts
Kustomer.requestUpdateUserStatus({ fallbackStatusType, appStatusCode? }): void
```

Asks Kustomer to change the agent's routing status. Voice and softphone apps use it to keep Kustomer in step with the agent's state on the phone platform, alongside the [`routing-status-change`](#kustomeron) event for the other direction. Nothing is sent without `fallbackStatusType`.

**params**

```js
softphone.on('call-started', () => Kustomer.requestUpdateUserStatus({ fallbackStatusType: 'busy' }))
```

## App setup flow

A setup page walks an admin through connecting your app while they install it. These four methods tell Kustomer where the admin is in that flow. Each returns nothing and never calls a callback.

### Kustomer.handleAppSetupStepsStarted

```ts
Kustomer.handleAppSetupStepsStarted(): void
```

The admin started the setup steps.

### Kustomer.handleAppSetupStepsComplete

```ts
Kustomer.handleAppSetupStepsComplete(): void
```

The admin finished the steps that must happen before install, and Kustomer can install the app. Call it once.

### Kustomer.handleAppSetupStepsCanceled

```ts
Kustomer.handleAppSetupStepsCanceled(): void
```

The admin canceled setup.

### Kustomer.handleAppSetupStepsClosed

```ts
Kustomer.handleAppSetupStepsClosed(): void
```

The admin closed the setup page.

## Modals

A card can open modals: each is its own page, which also loads the Cards SDK. Register a modal with `init`, then show and hide it as needed. A card can have up to three registered at once.

```js
await Kustomer.modal.init({ id: 'refund', url: 'https://kustomer.acme.example/refund', title: 'Refund order', additionalData: { orderId: '4417' }, height: 480, width: 560 })
Kustomer.modal.show('refund')
// Later, from the modal or the card:
Kustomer.modal.hide('refund', 'Refund issued')
Kustomer.modal.destroy('refund')
```

The modal page loads the SDK and reads the `additionalData` you passed from the `modal_initialize` event:

```js title="refund.js"
Kustomer.on('modal_initialize', (context) => {
  renderRefund(context.modalAdditionalData.orderId)
})
```

### Kustomer.modal.init

```ts
Kustomer.modal.init({ id, url, title?, additionalData?, height?, width? }, callback?): Promise
```

Registers a modal without showing it. The promise resolves, and the callback runs, once Kustomer has set the modal up.

**options**

Throws once three modals are registered. Free a slot with [`destroy`](#kustomermodaldestroy).

### Kustomer.modal.show

```ts
Kustomer.modal.show(id: string | number, title?: string): void
```

Shows a registered modal and hides any other. Pass `title` to change the title set in `init`.

### Kustomer.modal.hide

```ts
Kustomer.modal.hide(id: string | number, toast?: string): void
```

Hides the modal but keeps it registered, so you can show it again. With `toast`, Kustomer shows that text as a success toast.

### Kustomer.modal.resize

```ts
Kustomer.modal.resize({ height?, width? }): void
```

Resizes the visible modal. Without `height`, the SDK measures the page that calls it.

### Kustomer.modal.destroy

```ts
Kustomer.modal.destroy(id: string | number): void
```

Closes the modal if it's showing and unregisters it, which frees a slot.

### Kustomer.modal.destroyAll

```ts
Kustomer.modal.destroyAll(): void
```

Closes and unregisters every modal.

> **Registering after destroyAll:** In the current SDK, `modal.init` throws after `destroyAll()`. If your card registers modals again later, call `destroy(id)` for each one instead.

### Kustomer.showModal

```ts
Kustomer.showModal(modalContent: object): void
```

> **Deprecated:** An older way to show one of Kustomer's built-in confirmation dialogs. New cards should use [`Kustomer.modal`](#modals) with their own page.

## Troubleshooting

| Symptom                                                                   | Check                                                                                                                                                                  |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "kustomer-card.js appears to be running outside the Kustomer environment" | The page lost its `parentUrl` and `id` query parameters, often through a redirect, or it isn't in a Kustomer iframe.                                                   |
| The card or widget never appears                                          | Call `Kustomer.initialize` on load. Turn on `initialize({ debug: true }, …)` to see each message.                                                                      |
| Nothing happens and there are no errors                                   | Most methods do nothing quietly on bad input, such as an unknown `status` in `updateStatus` or an unknown event in `on`. With `debug: true` the SDK warns about these. |
| `request` responses or events never arrive                                | The `parentUrl` query parameter was changed, so the SDK ignores Kustomer's messages as coming from an unknown origin. With `debug: true` it logs each one it ignores.  |
| A promise around `expandInsightsCard` never settles                       | It takes no callback and returns nothing. Don't wait on it.                                                                                                            |
| `A maximum of 3 modals may be on the stack`                               | `destroy` a modal you no longer need before registering another.                                                                                                       |
