Authentication
Every request carries the client's API key as a bearer token. The client creates it in their dashboard under Account → API keys; it is shown once. The key identifies the tenant, so no endpoint takes a tenant id — you cannot reach another account's data by construction, not by a check somebody remembered to write.
curl https://sipmind.net/api/v1/agent/config \
-H "Authorization: Bearer sk-tenant-YOUR_KEY"
Two kinds of key. A Full key does everything below. A Widget-only key is public by design — it lives in a web page's source — and is accepted only on the widget endpoints; anywhere else it returns 403. Never put a Full key in a browser.
Quickstart: zero to answering
Four calls. The client registers themselves and gives you a key; everything after that is yours.
01
Tell the agent what the business is
In layered mode you supply only the facts — we supply the behaviour (how to greet, when to hand over to a human, how to speak on a phone line). In raw mode you write the entire prompt yourself.
curl -X PATCH https://sipmind.net/api/v1/agent/config \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{ "prompt_mode": "layered", "business_context": "Dental clinic in Almaty. Mon-Sat 9-19. Cleaning 15000 KZT, implant from 250000 KZT. Address: Abay 15.", "welcome_message": "Здравствуйте! Клиника «Дента». Слушаю вас.", "timezone": "Asia/Almaty" }'
Send only what you are changing — omitted fields keep their value.
02
Load what it should know
Price lists, FAQs, procedure descriptions. The agent answers from these.
curl -X POST https://sipmind.net/api/v1/documents \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"content": "Отбеливание Zoom 4 — 90000 ₸, занимает 60 минут…"}'
03
Connect a channel
One call does the whole handshake: we verify the token with Telegram, store it and register the webhook. If the webhook does not register you get an error — never a green answer with a channel that silently receives nothing.
curl -X POST https://sipmind.net/api/v1/channels/telegram \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"bot_token": "123456:AA..."}' # WhatsApp, when the number already sits on a Meta app you manage. # phone_number_id is the numeric id from WhatsApp Manager — not the phone number. curl -X POST https://sipmind.net/api/v1/channels/whatsapp \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"phone_number_id": "1236893399497040", "access_token": "EAA..."}' # check every channel at once, including the half-connected ones curl https://sipmind.net/api/v1/channels -H "Authorization: Bearer $KEY"
half_connected: true means credentials are stored but the provider handshake is missing — the state that looks connected on every screen and delivers nothing. Instagram, and WhatsApp numbers that do not yet exist on any Meta app, still need Meta's browser flow: send the client to their dashboard once, then keep working from code.
04
Pull the results into your system
curl "https://sipmind.net/api/v1/leads?updated_since=2026-08-01T00:00:00Z" \ -H "Authorization: Bearer $KEY" # → {"leads": [...], "count": 12, # "next_updated_since": "2026-08-12T09:14:02.881Z", "has_more": false}
Keeping in sync
Lists are ordered by updated_at ascending and cursored on it. Store the next_updated_since you get back and pass it in next time; keep going while has_more is true.
Ascending is not a style choice. Newest-first with an offset loses rows: while you page through, new rows land at the top and push the boundary, so page two repeats some and skips others. With an ascending cursor a changed row moves to the end — which is where your next poll meets it.
At-least-once, on purpose. Rows sharing the cursor's exact timestamp can come back on the next poll, so upsert by id. The alternative would drop rows, and a sync that quietly loses data is worse than one that repeats it.
Let your coding agent do it
If you already work in Claude Code, Codex or another MCP client, you do not have to write the calls yourself. One file, no dependencies, any Python 3.10+:
curl -O https://sipmind.net/mcp/sipmind_mcp.py
Run that in your project folder, then point your agent at it — for Claude Code, .mcp.json beside it:
{
"mcpServers": {
"sipmind": {
"command": "python3",
"args": ["./sipmind_mcp.py"],
"env": { "SIPMIND_API_KEY": "sk-tenant-YOUR_KEY" }
}
}
}
The path is relative, so start the agent from that folder — or write the full path instead. If the first call answers that your Python cannot verify certificates, that is your machine's trust store, not us: run Install Certificates.command (macOS python.org builds) or pip install certifi.
After that you can simply say what you want: “describe this clinic to the agent from their website, connect the Telegram bot, load the price list, and show me the leads from the last week.” Fourteen tools cover configuring the agent, connecting channels — including routing a WhatsApp number you already manage — the knowledge base, and reading leads, conversations and contacts.
Deliberately no dangerous verbs. The MCP surface can set up and read; it cannot place a call, send a paid WhatsApp template or delete anything. An agent following a half-understood instruction should not be able to spend your client's money — those actions stay in the API, where a human writes the request on purpose.
Events pushed to you
Polling is the reliable path; events are the fast one. Subscribe and we POST to your endpoint when something happens — lead.created the moment the AI captures a lead, lead.stage_changed when it moves, appointment.created when one is booked.
A new account receives lead.created only, so say which events you want — GET /events/subscription lists everything available.
PUT /api/v1/events/subscription
{
"url": "https://your-endpoint.example/hook",
"events": ["lead.created", "lead.stage_changed", "appointment.created"],
"signing_secret": "<16+ characters you choose>"
}
The secret is write-only: we report whether one is set, never its value. Then every delivery looks like this.
POST https://your-endpoint.example/hook
X-SIPmind-Event: lead.created
X-SIPmind-Signature: sha256=<hmac of the raw body with your secret>
{
"event": "lead.created",
"tenant_id": "0de00000-…",
"occurred_at": "2026-08-13T04:12:07.881Z",
"data": {
"lead_id": "9f2c…", "contact_id": "3a71…",
"name": "Anna", "phone": "+77001234567", "email": "",
"source": "whatsapp", "stage": "new", "message": "…"
}
}
Verify the signature against the raw body before trusting it. We retry a 5xx or a timeout three times with growing gaps; a 4xx is treated as your endpoint saying "don't", and we stop.
Events complement the sync, they do not replace it. A delivery can still be lost — your endpoint is down for an hour, a retry budget runs out. Treat events as a nudge and let the cursored updated_since poll be the source of truth. That combination is what makes a sync you never have to reconcile by hand.
Endpoints
| Endpoint | What it does |
|---|---|
| GET /api/v1/agent/config | Current behaviour, plus the allowed values for every field. |
| PATCH /api/v1/agent/config | Prompt, business facts, greeting, voice, model, temperature, knowledge-base tuning, timezone, language. |
| GET /api/v1/channels | Setup checklist: connected, half-connected or absent, per channel. |
| POST /api/v1/channels/telegram | Verify the bot token, store it, register the webhook. |
| POST/DELETE /api/v1/channels/whatsapp | Route a WhatsApp number you already manage. The token is checked with Meta before anything is stored; a number connected elsewhere is refused. |
| GET/POST/DELETE /api/v1/channels/phone | Route an existing number (Twilio, Telnyx, your carrier) to the agent. |
| GET/POST /api/v1/documents | List the knowledge base, or add to it. /documents/stats for counts, DELETE /documents/{id} to remove one. |
| POST /api/v1/query | Search that knowledge base directly. |
| GET/POST /api/v1/leads | Leads, cursored for incremental sync — or push one in from your own system. |
| PATCH /api/v1/leads/{id}/stage | Move a lead along the pipeline, mirroring a change in your CRM. |
| GET /api/v1/calls | Every interaction — phone, WhatsApp, Telegram, widget. |
| GET /api/v1/calls/{id} | One interaction with its transcript and AI summary. |
| GET /api/v1/contacts | The client's contact book. |
Full request and response shapes: the OpenAPI schema — point your generator at it.
Things worth knowing before you build
Secrets go in, never out
Channel tokens, the OpenAI key and passwords can be set through the API and are never returned by it. Your setup script therefore cannot leak a client's channel token — and cannot accidentally erase one either.
Transcripts are personal data
Most of our tenants are clinics. Lists never carry transcripts; you fetch them one at a time from /calls/{id}, so pulling them is a deliberate act rather than a side effect of a sync.
Rate limit and request ids
60 requests per minute per key. Responses carry X-RateLimit-Remaining, and Retry-After when throttled. Every response has an X-Request-ID — quote it when you write to us and we can find the exact request.
Plans gate capability, and say so
A text-only plan refuses a phone number with 402 rather than accepting it and never ringing. On trial and the lower plans the voice model is fixed; send a different one and the response tells you plainly it was not applied.
What the API does not do yet
So you can plan around it rather than discover it:
- Creating an account. Clients register themselves; you work with their key.
- Connecting WhatsApp and Instagram — those run through the dashboard's one-click flow, because the token exchange happens on Meta's side and needs a browser.
- Managing AI employees and practitioners.
- Deleting contacts, calls or conversations. Ask us and we will do it — a partner key should not be able to erase a client's history.
Need one of these? Write to hello@sipmind.net — this list is ordered by what people ask for.
Build against a real account
A free trial gives you a working tenant and a key in a couple of minutes.