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.sendreaches the customer. Without achannelIdrespond.io uses the last channel the contact interacted on, which is convenient but can send somewhere unexpected if the contact has moved; acustom_payloadmessage requires the channel. Message types are snake_case on the wire:text,attachment,custom_payload,quick_reply,whatsapp_template,email,facebook_template. A449means the message cannot be delivered there — most often a Facebook or Instagram message outside the 24-hour window without amessageTag.- 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.upsertwhen you do not know whether the contact already exists — it is a separate route, not a flag. contacts.listalways sends a search, a filter and a timezone, because respond.io answers400when 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$andor$or(exactly one of the two). Operators includeisEqualTo,hasAnyOffor tags and lifecycle, andisTimestampBetween, which takes{from, to}. Givetimezonewhen a timestamp condition needs a local day boundary.messages.listcan return an empty page with more to come. WithincludeComments: truethe list mixes comments in and each item carries atype; an empty page can still havepagination.nextset, so keep paging untilnextisnull.limitstops at 50.conversations.assigntakes a user ID (a number) or a user email, and unassigns withnull, 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
summaryis ignored unlesscategoryis set. Usespace.listClosingNotesfor the valid names. - Writes answer an ID or a code, not the record.
contacts.createandcontacts.lifecycleanswer{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.nextas a whole URL. Pass it back ascursorIdas it is, or just itscursorIdvalue. space.listCustomFieldsis what a contact write needs. A custom field’svaluemust match its type: a date isyyyy-mm-dd, a timeHH:MMin 24-hour form, a checkbox"true"/"false", and a number has no quotes. Only this route tells you thedataTypeand, for a list field, itsallowedValues.contacts.channelsis 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-AfterandX-RateLimit-*on a429.
- 23 typed API operations
Setup
1
Install
2
Add the plugin
corsair.ts
multiTenancy: true, scope calls with corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- API Key (Recommended)
No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.More: 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.
Example API calls
Read a contactWhat’s next
API reference
Every
respondio.api.* operation with input and output types.Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.