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

# respond.io plugin

> Manage contacts, send and read messages, assign and close conversations, and read workspace channels, templates and custom fields with respond.io.

Use **respond.io** through Corsair: one client, typed API calls.

Create an Access Token under **Settings, Integrations, Developer API** in respond.io. A workspace can hold up to 10, and the Developer API needs the **Growth plan or above**. The token goes in `Authorization: Bearer`.

**A contact is always addressed by a prefixed identifier**: `id:123`, `email:a@b.com` or `phone:+60123456789`. A bare value is treated as an `id:`, and a value that already carries a prefix is passed through unchanged, so both forms work.

Things to know before you send anything:

* **`messages.send` reaches the customer.** Without a `channelId` respond.io uses the last channel the contact interacted on, which is convenient but can send somewhere unexpected if the contact has moved; a `custom_payload` message *requires* the channel. Message types are snake\_case on the wire: `text`, `attachment`, `custom_payload`, `quick_reply`, `whatsapp_template`, `email`, `facebook_template`. A `449` means the message cannot be delivered there — most often a Facebook or Instagram message outside the 24-hour window without a `messageTag`.
* **A created contact is not immediately usable.** respond.io needs processing time after a create, and the docs say to wait before acting on it. Use `contacts.upsert` when you do not know whether the contact already exists — it is a separate route, not a flag.
* **`contacts.list` always sends a search, a filter and a timezone**, because respond.io answers `400` when any of the three is missing; the defaults (empty search, no conditions, UTC) match every contact. It is a POST so the filter can be a body, and it takes `$and` or `$or` (exactly one of the two). Operators include `isEqualTo`, `hasAnyOf` for tags and lifecycle, and `isTimestampBetween`, which takes `{from, to}`. Give `timezone` when a timestamp condition needs a local day boundary.
* **`messages.list` can return an empty page with more to come.** With `includeComments: true` the list mixes comments in and each item carries a `type`; an empty page can still have `pagination.next` set, so keep paging until `next` is `null`. `limit` stops at 50.
* **`conversations.assign` takes a user ID (a number) or a user email, and unassigns with `null`,** which the plugin always sends when no assignee is given — an absent key would read as "no change". An ID given as a numeric string is sent as a number, since respond.io rejects a string ID.
* **A close takes a closing note and a summary**, but `summary` is ignored unless `category` is set. Use `space.listClosingNotes` for the valid names.
* **Writes answer an ID or a code, not the record.** `contacts.create` and `contacts.lifecycle` answer `{code, message}`; `update`, `delete`, `upsert`, `merge`, both tag routes and both conversation routes answer `{contactId}`. To get the contact back, read it.
* **Paging.** Every list answers `pagination.next` as a whole URL. Pass it back as `cursorId` as it is, or just its `cursorId` value.
* **`space.listCustomFields` is what a contact write needs.** A custom field's `value` must match its type: a date is `yyyy-mm-dd`, a time `HH:MM` in 24-hour form, a checkbox `"true"`/`"false"`, and a number has no quotes. Only this route tells you the `dataType` and, for a list field, its `allowedValues`.
* **`contacts.channels` is plural** (`/channels`) where every other contact sub-route is singular. The channel IDs it returns are what a custom-payload message needs.
* **Rate limit.** 5 requests a second per HTTP method per request path, tracked independently, with `Retry-After` and `X-RateLimit-*` on a `429`.

**What you get:**

* 23 typed API operations

## Setup

**1. Install**

```bash npm theme={null}
npm install corsair @corsair-dev/respondio better-sqlite3
npm install --save-dev @types/better-sqlite3
```

```bash yarn theme={null}
yarn add corsair @corsair-dev/respondio better-sqlite3
yarn add --dev @types/better-sqlite3
```

```bash pnpm theme={null}
pnpm install corsair @corsair-dev/respondio better-sqlite3
pnpm install --save-dev @types/better-sqlite3
```

```bash bun theme={null}
bun add corsair @corsair-dev/respondio better-sqlite3
bun add --dev @types/better-sqlite3
```

**2. Add the plugin**

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { respondio } from '@corsair-dev/respondio';

export const corsair = createCorsair({
	plugins: [
		respondio(),
	],
	database: new Database('corsair.db'),
	kek: process.env.CORSAIR_KEK!,
	multiTenancy: true,
	hub: {
		projectApiKey: process.env.CORSAIR_API_KEY!,
		signingSecret: process.env.CORSAIR_SIGNING_SECRET!,
	},
});
```

With `multiTenancy: true`, scope calls with `corsair.withTenant(id)`. See [Quick start](https://docs.corsair.dev/quick-start.md) for KEK + Hub keys, and [Multi-tenancy](https://docs.corsair.dev/concepts/multi-tenancy.md) for account isolation.

**3. Choose authentication**

> [tab] API Key (Recommended)
>
> No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.
>
> ```ts theme={null}
> respondio()
> ```
>
> More: [API Key](https://docs.corsair.dev/concepts/api-key)

**4. Connect a tenant**

Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see [Connect / OAuth](https://docs.corsair.dev/management/connect).

```ts theme={null}
const { connectUrl } = await corsair.manage.connect.createLink({
	plugin: 'respondio',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**Read a contact**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.respondio.api.contacts.get({ identifier: 'id:12345' });
```

**Send a text message**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.respondio.api.messages.send({
	identifier: 'id:12345',
	channelId: 42,
	message: { type: 'text', text: 'Your order has shipped.' },
});
```

See the full list on the [API](https://docs.corsair.dev/plugins/respondio/api.md) page.

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/respondio/api.md)
>
> Every `respondio.api.*` operation with input and output types.

> [card] [Connect / OAuth](https://docs.corsair.dev/management/connect)
>
> createLink, Hub delivery, and tenant connect flows.

> [card] [Use with agents](https://docs.corsair.dev/mcp-adapters/mcp-adapters)
>
> Expose this plugin's operations as MCP tools.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.