Authorization: Bearer and is sent to {your-host}/api/ext/v3. A dedicated account can be issued its own host: pass it as apiHost, which takes precedence over region.
- The region is a plugin option. WATI has three regional hosts — Global (
https://live-mt-server.wati.io), EU (https://eu-api.wati.io) and KSA (https://ksa-api.wati.io) — and a token only works against the one its workspace lives in. Nothing in the token says which, so passregion: 'eu'or'ksa'; the default is Global. - V3 puts no tenant in the path. The token identifies the workspace, so routes are
/api/ext/v3/.... Only the older V1/V2 routes take atenantId, and this plugin does not use them. - Two distinct 401s.
missing_tokenmeans no key was sent at all;invalid_tokenmeans one was sent and is wrong, malformed or expired. Both are auth errors, but the difference tells you whether the credential was configured at all, so the plugin exposes both. - A 403 is not only a scope problem. The docs are explicit that the role, the plan and access to a particular channel can each cause it, so the plugin treats it as a permission error rather than blaming the scope.
- Free text only works inside 24 hours.
messages.sendTextis refused outside the customer service window;messages.sendTemplateis the only route through. That is why they are separate operations rather than one with a flag. - Every template send is a campaign. WATI requires
broadcast_nameeven for one recipient and records the send under it. Leave outparametersfor a template with no placeholders. A 200 can still carrysuccess: falsewitherror, and each recipient row can carry its ownerrors. - A conversation target is not a contact ID.
conversations.messagestakes a wa_id, a phone number or a conversation ID, and the docs warn against substituting a bare contact ID, which is a different value. - A leading
+has to be encoded in a path. A contact target may start with+, which is percent-encoded when it is part of the URL so it stays one path segment. Send routes take the target in the JSON body, where it is sent as given. - Paging is
page_numberandpage_size. One-based, capped at 100 per page.broadcasts.listalso needsdate_fromanddate_to: the live API refuses the list without them, though the reference does not mark them. - Custom attributes are pairs, not a map. A contact’s
custom_paramsis a list of{ name, value }, which is WATI’s own shape. - Creating a webhook still needs V2. The V3 routes list, read and delete subscriptions, but creation has not moved to V3 — so the plugin wraps the three that exist and says so rather than reaching into a different API version. The V3 list has no paging.
- 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
List contactsWhat’s next
API reference
Every
wati.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.