In short
The Reseller API lets your own software do what the Reseller menu does — create and close client workspaces, make connect links, add their people, set your name and logo, read your balance, statement and paid channels — and work inside any client workspace with one header, X-Workspace-Id. The rest of the API is on the developer overview.
Your two keys
Make both in Reseller › Settings, under Use the API — step by step. Each is shown once: copy it into your server. They are listed, and revoked, under Security › Developer › API Keys.
| Key | Made in | Scopes | What it does |
|---|---|---|---|
| Reseller key | Reseller › Settings › Create reseller key | read · write · reseller | The /reseller/ routes — and, inside any client workspace, messages, conversations, contacts, webhooks and the workspace tools. It never posts. |
| Publishing key | Reseller › Settings › Create publishing key, after ticking «Posts sent with this key are published on the selected accounts in my name.» | publish · reseller | Posts to every Instagram, Facebook and TikTok account of the client workspace X-Workspace-Id names, accounts connected later included. It reads no conversations. |
The publishing key is made in the name of the person who ticked the confirmation. If that person leaves the team, it answers 403 consent_required: make a new one.
Work inside a client workspace
Send X-Workspace-Id with the id of a client workspace, on any route of the API but /reseller/. The request is then that workspace’s: its channels, conversations, contacts, webhooks, posts, package and holds. Without the header, the key acts as your own workspace. An id that is not one of yours is 404 workspace_not_found — never another reseller’s workspace, which would say it exists.
curl -X POST https://miragechat.io/api/ext/v1/messages \
-H "Authorization: Bearer mck_live_…" \
-H "X-Workspace-Id: 1304" \
-H "Content-Type: application/json" \
-d '{"account_id": 77, "to": "+15551234567", "text": "Your appointment is at 10:30."}'The rate limit counts each client workspace on its own: about 300 requests a minute per key and workspace (the Publishing API: 60), and 3,000 a minute for the key in all. One client cannot spend another’s minute.
Endpoints
Base URL https://miragechat.io/api/ext/v1. Every route needs the reseller scope, with read for a GET and write for a change. A path {id} that is not one of your client workspaces is 404 workspace_not_found.
| Method | Path | Scope | What it does |
|---|---|---|---|
GET | /reseller/me | read · reseller | Your account: status, prices, balance, what a month costs at today’s channels, and what falls due in the next 7 days. |
GET | /reseller/workspaces | read · reseller | Your client workspaces, newest first. include_closed=0 leaves the closed ones out; external_id finds one by your reference. |
POST | /reseller/workspaces | write · reseller | Create a client workspace: name, timezone, external_id, return_url. With return_url, the answer carries its connect link. Send external_id and a retry is safe: the same one again is 409 external_id_taken with the workspace_id, never a second workspace. |
GET | /reseller/workspaces/{id} | read · reseller | One client workspace. |
PATCH | /reseller/workspaces/{id} | write · reseller | Rename it, or change your reference (external_id; null clears it). |
DELETE | /reseller/workspaces/{id} | write · reseller | Close it: read-only at once, its channels switched off, its data kept for 90 days. |
POST | /reseller/workspaces/{id}/connect-link | write · reseller | A new connect link with your name and logo: return_url, and hours from 1 to 168 (24 when left out; another number is 400). |
POST | /reseller/workspaces/{id}/connect-link/revoke | write · reseller | Every open connect link of the workspace stops working at once. |
GET | /reseller/workspaces/{id}/users | read · reseller | The people who sign in to it: role, switched off or not, last sign-in. |
POST | /reseller/workspaces/{id}/users | write · reseller | Add a person: email, password, role (admin or agent) and name. Nothing is emailed to them. |
PATCH | /reseller/workspaces/{id}/users/{userId} | write · reseller | Switch a person off or on, or change their role, password or name. |
GET | /reseller/channels | read · reseller | Every paid channel of your client workspaces, with paid_until and monthly_usd — to bill your own clients by. |
GET | /reseller/statement | read · reseller | What was taken from your balance, newest first: limit (1 to 500, 100 when left out; another number is 400), and before for older lines — next_before is null on the last page. |
GET | /reseller/settings | read · reseller | Your name and logo on the connect page. |
PATCH | /reseller/settings | write · reseller | Change display_name and logo_url (an https:// address). |
Client workspaces
POST /reseller/workspaces takes a name (required), a timezone (a zone name such as Europe/London), your own reference in external_id (two of your workspaces cannot share one: 409 external_id_taken, with the workspace_id that has it — so a retry with the same external_id never makes a second workspace), and a return_url. name and external_id are text of at most 120 characters (400 invalid_request otherwise — they are not cut); an external_id of "" or null is none. With a return_url, the answer also carries connect_url and connect_expires_at; a link that could not be made is said in connect_error, and the workspace is made all the same — make the link later.
curl -X POST https://miragechat.io/api/ext/v1/reseller/workspaces \
-H "Authorization: Bearer mck_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "Seaside Villa", "timezone": "Europe/London", "external_id": "client-42",
"return_url": "https://your-app.example.com/clients/42"}'
201 Created
{
"workspace": {
"id": 1304, "name": "Seaside Villa", "slug": "seaside-villa-3fa91c", "external_id": "client-42",
"timezone": "Europe/London", "created_at": "2026-10-15T09:12:03.000Z", "closed_at": null,
"status": "no_channels", "channels": [], "chats_30d": 0, "monthly_usd": 0
},
"connect_url": "https://miragechat.io/connect/c_…",
"connect_expires_at": "2026-10-16T09:12:03.000Z"
}A workspace carries id, name, slug, external_id, timezone, created_at, closed_at, status (active, no_channels or closed), channels (its connected accounts), chats_30d and monthly_usd. GET /reseller/workspaces lists closed ones too, unless include_closed=0; external_id=client-42 finds yours.
DELETE /reseller/workspaces/{id} closes it: read-only at once, its channels switched off — each one told to your webhooks as account.disconnected — and its data kept for 90 days. It answers the workspace with channels_switched_off and delete_after_days. A closed workspace is not renamed (409 workspace_closed) and takes no new link or person.
Connect links
A connect link opens a page with your name and logo, where your client connects WhatsApp, Instagram and a Facebook Page without a MirageCHAT account. It works for hours (1 to 168; 24 when left out). Each call makes a new link, and the earlier ones keep working until they expire — POST …/connect-link/revoke stops every open link of the workspace at once and answers how many: { "workspace_id": 1304, "revoked": 2 }.
curl -X POST https://miragechat.io/api/ext/v1/reseller/workspaces/1304/connect-link \
-H "Authorization: Bearer mck_live_…" \
-H "Content-Type: application/json" \
-d '{"return_url": "https://your-app.example.com/clients/42", "hours": 48}'
201 Created
{ "workspace_id": 1304, "url": "https://miragechat.io/connect/c_…", "expires_at": "2026-10-17T09:20:00.000Z" }People
Your client’s staff sign in only if you add them. POST …/users takes an email, a password you set, a role (admin or agent; agent when left out) and a name. We email them nothing — no invitation, no confirmation. One address opens one workspace: an address that already signs in anywhere is 409 email_taken. An agent answers the channels connected when they are added.
curl -X POST https://miragechat.io/api/ext/v1/reseller/workspaces/1304/users \
-H "Authorization: Bearer mck_live_…" \
-H "Content-Type: application/json" \
-d '{"email": "ana@seasidevilla.example", "password": "…", "role": "admin", "name": "Ana Lee"}'
201 Created
{ "user": { "id": 5521, "name": "Ana Lee", "email": "ana@seasidevilla.example", "role": "admin",
"disabled": false, "last_login": null, "created_at": "2026-10-15T09:25:41.000Z" } }PATCH …/users/{userId} takes disabled, role, password and name. Switched off, a person is signed out at once and their remembered devices are forgotten; a new role ends their sessions too; a new password is not announced to anyone. An owner or a manager is not yours to re-role (409 role_not_changeable). The list never shows the workspace’s own system account.
Balance, statement and channels
GET /reseller/me—status,prices(platform_usd,channel_usd),balance_usd,monthly_usd,channels,workspaces(open and total),platform_paid_until,grace_until, and what falls due in the next 7 days:due_soon,due_soon_usdandshort_usd— what your balance lacks to cover it.GET /reseller/statement— every charge, newest first:id, kind, workspace_id, workspace_name, platform, amount, period_from, period_to, ledger_reference, created_at. Sendnext_beforeback asbeforefor older lines; it isnullon the last page.limitis 1 to 500 (100 when left out).GET /reseller/channels— every channel of your client workspaces you have paid for, with the account it is now,connected,paid_untilandmonthly_usdat your price. A connected channel renews onpaid_until; a disconnected one keeps its month and does not renew. It lists channels that are connected or inside a paid month;include_ended=1adds the ones whose month ran out.workspace_idfilters,limitandcursorpage.
curl "https://miragechat.io/api/ext/v1/reseller/channels?workspace_id=1304" \
-H "Authorization: Bearer mck_live_…"
200 OK
{
"data": [
{ "id": 731, "workspace_id": 1304, "workspace_name": "Seaside Villa", "external_id": "client-42",
"workspace_closed": false, "platform": "whatsapp", "account_id": 77, "account_name": "Seaside Villa",
"connected": true, "paid_until": "2026-11-15T09:40:00.000Z", "monthly_usd": 1.25 }
],
"next_cursor": null
}Your name and logo
GET and PATCH /reseller/settings read and change display_name and logo_url — what your clients see on the connect page. The logo is an https:// address (400 invalid_logo_url otherwise); an empty name shows your workspace’s own. Prices are set by our team, not here.
One webhook for every client
An endpoint with include_clients: true also receives every event of every client workspace, each with the workspace_id it came from. Make it with the reseller key and without X-Workspace-Id (403 reseller_only otherwise), choosing your events — the posts of your client workspaces arrive too, if it takes post.*. Or add one in Reseller › Settings: it takes message.received, message.sent, conversation.created, conversation.closed, account.connected and account.disconnected. The delivery’s tenant_id is your own workspace’s; account.* events add your external_id.
curl -X POST https://miragechat.io/api/ext/v1/webhooks \
-H "Authorization: Bearer mck_live_…" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-app.example.com/hooks/mirage", "include_clients": true,
"events": ["message.received", "conversation.created", "account.connected", "post.published"]}'Signatures, retries and every event: Webhooks on the developer overview.
When your account is paused
While your balance cannot cover what falls due in the next 7 days, we email you — at most once a day. A renewal that still cannot be paid gets one day of grace; then your account and every client workspace are paused until the money arrives.
- Refused with
402 on_holdandbilling_url: whatever adds to your workspaces — a new client workspace, a rename, a connect link, a person added, switched back on or changed, your settings. - Always works: reading, closing a client workspace, revoking its links, switching a person off.
- Inside a client workspace, writes are refused with
402too (holds); reads still answer, and messages keep arriving.
Errors
A refusal is { "error": "<code>", "code": "<code>", "message": "…" } — error and code are the same word.
| Code | HTTP | Meaning |
|---|---|---|
missing_api_key · invalid_api_key | 401 | No key, or a wrong, revoked or expired one. |
insufficient_scope | 403 | A publishing key, or a key without read (for a GET) or write (for a change) — required names it. |
reseller_scope_required | 403 | The key has no reseller scope. Use a key made in Reseller › Settings. |
not_a_reseller | 403 | The key’s workspace is not a reseller now. |
workspace_not_found · user_not_found | 404 | Not one of your client workspaces, or not a person of that workspace. |
invalid_request · not_editable | 400 | A field is missing or wrong, or cannot be changed here — the message names it. |
invalid_email · invalid_role · password_required · weak_password | 400 | A person’s details. weak_password adds password_failed and password_rules. |
invalid_return_url · invalid_logo_url · invalid_cursor | 400 | An address that is not https://, or a cursor this list did not give. |
external_id_taken · email_taken | 409 | Your reference names another client workspace, or the address already signs in to a MirageCHAT workspace. |
workspace_closed · role_not_changeable | 409 | The workspace is closed (it is read-only), or the person’s role is not yours to change. |
on_hold | 402 | Your account is paused: a renewal was not paid after its day of grace. billing_url says where to add money. |
internal_error | 500 | Something failed on our side, and nothing was changed. Try again in a moment. |
What it does not do
YouTube data is not available through the API. YouTube’s rules for apps (YouTube API Services Developer Policies) do not allow it: no YouTube conversation, comment, automation or number is read through the API, and nothing is posted to YouTube with it — platform=youtube answers 400 platform_not_available, a YouTube account in POST /posts answers 422 unsupported_by_platform. Post to YouTube from Instant Post in MirageCHAT.
- AI in a client workspace. Client workspaces have no AI assistant, so
POST /ai/replyanswers402 module_requiredthere. - Your prices and your partner account are set by our team.
- Reopening a closed client workspace is not possible from the API.
- Connecting channels. The connect link covers WhatsApp, Instagram and a Facebook Page; Telegram, website chat, SMS, TikTok, YouTube and Google are connected inside the client workspace, under Channels.