For developers

Set a client up from your own code

Describe the business, connect WhatsApp, Telegram or a phone number, load the knowledge base, and sync the leads and conversations back into your own system. One bearer token, plain REST, a published OpenAPI schema.

Quickstart OpenAPI 3.1 schema

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

You supply the facts; the behaviour — how to greet, when to hand over to a human, how to speak on a phone line — is ours, kept up to date, and is neither editable nor shown. GET /api/v1/agent/config names the field that holds your text in prompt_field: business_context on every current account.

curl -X PATCH https://sipmind.net/api/v1/agent/config \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "business_context": "Dental clinic in Almaty. Mon-Sat 9-19. Cleaning 15000 KZT, implant from 250000 KZT. Address: Abay 15.",
    "welcome_message": "Hi, you have reached Brightwater Dental. How can I help?",
    "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": "Teeth whitening — $350, takes about 60 minutes…"}'

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 AI assistant do it

SIPmind speaks MCP, so Claude, Cursor, Codex or any MCP client can set a client up and read their data in plain language: “describe this clinic to the agent from their website, connect the Telegram bot, load the price list, ask the agent what a cleaning costs, and show me last week's leads.” Thirty-six tools cover configuring the agent — down to the facts it must say word for word — trying it the way a customer would, hiring AI employees and giving each one its own line, outbound calling, channels, the knowledge base, the Instagram comment→DM funnel, who a conversation is handed to and when they are in, webhooks, and leads, pipelines, conversations, contacts and companies.

Connect by address — nothing to install

The server lives at https://sipmind.net/mcp.

claude mcp add --transport http sipmind https://sipmind.net/mcp \
  --header "Authorization: Bearer sk-tenant-YOUR_KEY"
{
  "mcpServers": {
    "sipmind": {
      "url": "https://sipmind.net/mcp",
      "headers": { "Authorization": "Bearer sk-tenant-YOUR_KEY" }
    }
  }
}

Or run it on your machine

One file, no dependencies, any Python 3.10+ — the same tools as the address above:

curl -O https://sipmind.net/mcp/sipmind_mcp.py

Claude Code — .mcp.json in that folder:

{
  "mcpServers": {
    "sipmind": {
      "command": "python3",
      "args": ["./sipmind_mcp.py"],
      "env": { "SIPMIND_API_KEY": "sk-tenant-YOUR_KEY" }
    }
  }
}

Claude Desktop — the same entry in claude_desktop_config.json, with the full path to the file in args. Codex — ~/.codex/config.toml:

[mcp_servers.sipmind]
command = "python3"
args = ["/full/path/to/sipmind_mcp.py"]
env = { SIPMIND_API_KEY = "sk-tenant-YOUR_KEY" }

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.

Which key

A Full key sets things up. A Read only key — Account → API keys → Access — pulls leads, conversations and reports and changes nothing: the right one for an export, or for an assistant that should only look.

Try the agent before a customer does

ask_agent (REST: POST /api/v1/agent/test) sends what a customer would write and returns the agent's reply, built exactly as a live chat builds it — same prompt, knowledge base and tools. Nothing is sent, saved or booked: the tools the agent chose come back in would_call — for example submit_lead with the name and phone it would have saved — and are not run; the reply is then what the agent writes once they succeed, and tools_simulated says the success was a stand-in. Pass employee_id to try an AI employee while it is still a draft. It spends model tokens, so it takes a Full key.

curl -X POST https://sipmind.net/api/v1/agent/test \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"message": "How much is a cleaning? I am Anna, +77001234567", "channel": "whatsapp"}'
# → {"reply": "…", "would_call": [{"tool": "submit_lead", "arguments": {"name": "Anna", …}}], …}

Two things a browser has to do once. Connecting an Instagram account is a consent Meta requires a human to grant on screen, and a WhatsApp Business Account is shared with us the same way — so those are one click in the dashboard, not an API call. Everything that happens after the connection is code.

Deliberately no dangerous verbs. The MCP surface can set up, read and try; 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. A new account receives lead.created only, so name the events you want.

EventWhendata carries
lead.createdA lead is captured — by the agent, a form, the API or a person.lead_id, contact_id, name, phone, email, source, stage, message, tags
lead.stage_changedA lead moves on its pipeline.lead_id, stage, close_reason
lead.mergedTwo leads of one person become one.lead_id (kept), merged_lead_id (gone), moved
contact.createdA new person is in the contact book.contact_id, full_name, phones, emails, channel
contact.updatedA contact gets a new name, phone, email, fields or owner.contact_id, fields (what changed), full_name, phones, emails
contact.mergedTwo contacts of one person become one.contact_id (kept), merged_contact_id (gone), moved
call.endedA phone or WhatsApp call the agent took ends.call_id, channel, direction, from, to, contact_id, duration_seconds, ended_at — the transcript and summary come from GET /calls/{id}
appointment.createdA visit is booked — by the agent or in the calendar.appointment_id, practitioner_id, practitioner_name, contact_id, starts_at_utc, ends_at_utc, service, source
appointment.cancelledA visit is cancelled, by anyone.appointment_id, practitioner_id, contact_id, starts_at_utc, ends_at_utc, service
task.createdA task is set — by a person or the agent.task_id, title, status, due_at, contact_id, lead_id, company_id, assigned_to, source
task.completedA task is done.the same, with completed_at and completed_by
PUT /api/v1/events/subscription
{
  "url": "https://your-endpoint.example/hook",
  "events": ["lead.created", "lead.stage_changed", "call.ended"],
  "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

EndpointWhat it does
The agent
GET /api/v1/agent/configCurrent behaviour, the allowed values for every field, and the plan's limits.
PATCH /api/v1/agent/configBusiness facts, greeting, voice and speaking tempo, facts said word for word, callers never answered, knowledge-base tuning, timezone, notification language, and the languages your customers speak.
POST /api/v1/agent/testAsk the agent: its reply to what a customer would write, and the tools it would call — nothing is sent, saved or booked.
GET/PATCH /api/v1/team/routingWho a conversation is handed to — phones, PBX extensions, queues, named per department or colleague — the hours and days off when someone is in, and the Telegram group that hears about new leads.
GET/POST /api/v1/employeesAI employees: list them, or hire one. A new one is a draft and answers nobody until it is switched on.
PATCH /api/v1/employees/{id}Change an employee; status: active puts it on duty.
PUT/DELETE /api/v1/employees/{id}/linesGive an employee its own line or channel, or take it away.
Channels
GET /api/v1/channelsSetup checklist: connected, half-connected or absent, per channel.
POST/DELETE /api/v1/channels/telegramVerify the bot token, store it, register the webhook — or disconnect.
POST /api/v1/channels/whatsappRoute a WhatsApp number you already manage. The token is checked with Meta before anything is stored; a number connected elsewhere is refused. DELETE /channels/whatsapp/{phone_number_id} unroutes it.
GET/POST /api/v1/channels/phoneRoute an existing number (Twilio, Telnyx, your carrier) to the agent. DELETE /channels/phone/{id} removes one.
GET/PATCH /api/v1/channels/instagram/settingsThe Instagram comment→DM funnel: switches, working hours and days, the default scenario.
GET /api/v1/channels/instagram/scenariosCodeword scenarios. PUT or DELETE /channels/instagram/scenarios/{id} to save or remove one.
GET /api/v1/channels/instagram/funnelThe funnel's numbers per scenario, for the last N days.
Knowledge
GET/POST /api/v1/documentsList the knowledge base, or add to it. /documents/stats for counts, GET or DELETE /documents/{id} for one.
POST /api/v1/querySearch that knowledge base the way the agent does.
CRM
GET/POST /api/v1/leadsLeads, cursored for incremental sync — or push one in from your own system. Each lead names the pipeline it is on, the person on your team who owns it (assigned_to) and its tags.
GET /api/v1/pipelinesThe account's pipelines and each one's stages, with what every stage means: open, won or lost.
PATCH /api/v1/leads/{id}/stageMove a lead to a stage of its own pipeline, mirroring a change in your CRM.
POST /api/v1/leads/{id}/answeredClose a complaint or question the customer got an answer to in your own system. Pipelines that work this way say closes_when_answered; each lead carries answered_at and answered_via.
GET/POST /api/v1/automationsThe account's own rules: when a card enters a stage or a new card appears, create a task, give it to somebody, move it, tag it, write to the customer or tell the managers — right away or after a delay. The listing also says what each part may hold. A rule acts only on what happens after it was switched on.
GET/PATCH/DELETE /api/v1/automations/{id}One rule. Send only what changes; enabled alone switches it.
GET /api/v1/automations/{id}/runsWhat a rule did lately, per card: task_created, sent, window_closed, left_stage …
GET /api/v1/callsEvery interaction — phone, WhatsApp, Telegram, Instagram, widget.
GET /api/v1/calls/{id}One interaction with its transcript and AI summary.
GET /api/v1/contactsThe client's contact book, with the person responsible for each contact (assigned_to) and its tags.
GET /api/v1/companiesCompanies those contacts belong to.
Outbound calls
GET/PATCH /api/v1/outbound/settingsCaller ID, what the agent calls about, its first line, and the hours it may dial — with ready and the blockers that stop a call.
POST /api/v1/outbound/callPlace one call. POST /outbound/call/{id}/cancel stops a queued one; GET /outbound/calls lists them.
GET/POST /api/v1/outbound/dncThe do-not-call list. DELETE /outbound/dnc/{id} takes a number off it.
Events
GET/PUT /api/v1/events/subscriptionWhere we POST events, which ones, and the secret they are signed with.

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, an AI key you bring 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. The voice engine is chosen by us unless the account runs on its own AI key: then model_locked is false and the choices are listed. Otherwise a write of it is not applied, and the response says so in a notice.

What the API does not do yet

So you can plan around it rather than discover it:

Need one of these? Write to [email protected] — 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.