Intellismi API
Everything the dashboard does is an HTTP call, so your own systems can place
calls, learn what happened on them, keep the contact list in sync and pull
usage. This page covers the stable v1 surface; the interactive OpenAPI
listing of every route is at /api/docs on your installation.
The same reference, rendered, lives at https://<your-intellismi>/developers.
Base URL and authentication
https://<your-intellismi>/api/v1/...
Authorization: Bearer aicc_xxxxxxxxxxxxxxxxx
Create keys under Settings → API access. A key belongs to one organisation and carries scopes:
| Scope | Allows |
|---|---|
calls:write |
place and hang up calls |
calls:read |
list calls, read transcripts, download recordings, read usage |
contacts:write |
create/update contacts |
contacts:read |
look contacts up |
Errors are JSON: {"detail": "human-readable reason"} with a conventional
status (400 bad input, 401 no/invalid key, 403 missing scope, 404 not
found, 409 the line cannot call right now, 429 concurrent-call limit of the
plan reached).
API keys created under Settings → API access can also manage campaigns and inbox requests (see below). Everything is plain JSON over HTTPS; there is no SDK to install. Copy the examples, replace the key and the base URL.
Quick start
# 1. Which agents and lines do I have?
curl https://<your-intellismi>/api/v1/agents -H "Authorization: Bearer aicc_…"
curl https://<your-intellismi>/api/v1/lines -H "Authorization: Bearer aicc_…"
# 2. Have the agent call someone about an appointment
curl -X POST https://<your-intellismi>/api/v1/calls \
-H "Authorization: Bearer aicc_…" -H "Content-Type: application/json" \
-d '{"number": "+97339000000", "caller_name": "Ali Hassan", "profile_id": 7,
"purpose": "Remind Ali about his appointment tomorrow at 10:00 and ask him to confirm or pick another time.",
"reference": "APT-1042", "metadata": {"clinic": "Seef"}}'
# 3. Get the result when the call ends (webhook), or poll:
curl https://<your-intellismi>/api/v1/calls/<id> -H "Authorization: Bearer aicc_…"
Agents
GET /api/v1/agents — the organisation's hired agents. Use id as
profile_id on calls and agent_profile_id on campaigns; the agent
introduces itself by its name and speaks with its own voice.
{"agents": [{"id": 7, "name": "Ahmed", "role": "Customer service · Arabic & English", "languages": ["ar", "en"], "is_default": true}]}
How an outbound call works
POST /api/v1/calls— Intellismi picks a registered line and sends the INVITE. The response is 201 immediately withstatus: "dialing". The assistant is not connected yet.- The far end rings. If they do not answer, the call ends with
status: "failed"anddetailcarrying the SIP reason (busy, no answer, number not found…). - The moment they answer, the audio path is attached and the assistant speaks
its
greeting(or the profile's default), then works towardpurposeusing the organisation's knowledge base, tools (take a message, save the contact, transfer to a person) and the language you fixed or the one the person speaks. - When either side hangs up, the call is summarised, billed for the whole
seconds the assistant was on the line, and — if you set one — your
webhook receives the full result. You can also poll
GET /api/v1/calls/{id}.
Place a call
POST /api/v1/calls (scope calls:write)
| Field | Type | Notes |
|---|---|---|
number |
string | required — +97317000000 (E.164, recommended), local digits, an extension, or a sip: URI |
account_id |
string | line to call from (see /api/v1/lines). Default: the first registered line |
caller_name |
string | who you are calling; the assistant greets them by name |
purpose |
string | what to achieve, e.g. "Confirm tomorrow's 10:00 appointment or offer to reschedule" |
greeting |
string | exact first sentence; default comes from the agent profile |
language |
en ar hi multi |
fix the call language; multi follows the person |
profile_id |
int | AI agent profile to use instead of the line's default |
reference |
string ≤120 | your own id (order, ticket, appointment) — returned everywhere and filterable |
metadata |
object | any JSON, echoed back unchanged in the call record and webhook |
webhook_url |
string | per-call override of the organisation webhook |
mode |
ai (default) tone |
tone places a test call that plays a tone instead of the assistant |
curl -X POST https://<your-intellismi>/api/v1/calls \
-H "Authorization: Bearer aicc_…" -H "Content-Type: application/json" \
-d '{
"number": "+97339000000",
"caller_name": "Ali Hassan",
"purpose": "Confirm tomorrow'\''s 10:00 dental appointment. If it does not suit, offer 11:00 or Thursday 9:00.",
"language": "ar",
"reference": "APT-1042",
"metadata": {"clinic": "Seef", "doctor": "Dr. Noor"}
}'
{
"id": "acc-1-7", "direction": "outbound", "status": "dialing", "outcome": "",
"line": "acc-1", "number": "sip:+97339000000@ucaas.example.com",
"caller_name": "Ali Hassan", "reference": "APT-1042", "metadata": {"clinic": "Seef", "doctor": "Dr. Noor"},
"purpose": "Confirm tomorrow's 10:00 dental appointment…",
"started_at": 1789300000.1, "answered_at": null, "ended_at": null,
"duration_seconds": 0, "billed_seconds": 0, "summary": "", "is_lead": false, "recording_url": null
}
Python:
import requests
r = requests.post("https://<your-intellismi>/api/v1/calls",
headers={"Authorization": "Bearer aicc_…"},
json={"number": "+97339000000", "caller_name": "Ali Hassan",
"purpose": "Confirm tomorrow's 10:00 appointment", "reference": "APT-1042"})
r.raise_for_status()
call_id = r.json()["id"]
Node:
const r = await fetch("https://<your-intellismi>/api/v1/calls", {
method: "POST",
headers: { Authorization: "Bearer aicc_…", "Content-Type": "application/json" },
body: JSON.stringify({ number: "+97339000000", caller_name: "Ali Hassan",
purpose: "Confirm tomorrow's 10:00 appointment", reference: "APT-1042" }),
});
const { id } = await r.json();
Read a call
GET /api/v1/calls/{id} (scope calls:read) — the record above plus
transcript ([{role, text, at}]) and messages taken during the call.
status |
Meaning |
|---|---|
dialing |
INVITE sent, waiting for the far end |
ringing |
inbound call not yet answered |
answered |
in progress |
ended |
finished normally |
failed |
not connected — see detail |
outcome |
Meaning |
|---|---|
ai |
the assistant handled it |
human |
a person on your team took it |
forwarded |
transferred to a number/extension |
ivr |
the caller only used the menu |
missed |
never answered |
GET /api/v1/calls?since=&until=&direction=&status=&outcome=&reference=&limit=— list (unix seconds; default last 7 days).GET /api/v1/calls/{id}/transcript?format=text— plain-text download.GET /api/v1/calls/{id}/recording— stereo WAV (caller left, assistant right).POST /api/v1/calls/{id}/hangup.GET /api/v1/lines— lines and whether each can place a call right now.
Webhooks
Set the URL (and an optional signing secret) under Settings → API access,
or PUT /api/v1/webhook {"url": "...", "secret": "..."}.
POST /api/v1/webhook/test sends a sample.
When a call ends Intellismi POSTs JSON with header X-Intellismi-Event: call.completed:
{
"event": "call.completed",
"id": "acc-1-7", "direction": "outbound", "status": "ended", "outcome": "ai", "detail": "",
"line": "acc-1", "number": "sip:+97339000000@…", "caller_name": "Ali Hassan",
"reference": "APT-1042", "metadata": {"clinic": "Seef"}, "purpose": "Confirm…",
"started_at": 1789300000.1, "answered_at": 1789300006.4, "ended_at": 1789300071.9,
"duration_seconds": 65.5, "billed_seconds": 66,
"summary": "Ali confirmed the 10:00 appointment and asked for an SMS reminder.",
"is_lead": false, "sentiment": "positive", "forwarded_to": null,
"transcript": [{"role": "assistant", "text": "…", "at": 1789300007.0}, {"role": "user", "text": "…", "at": 1789300011.2}],
"messages": [{"from": "Ali Hassan", "subject": "Reminder", "text": "Send an SMS the evening before", "callback_number": "+97339000000", "priority": "normal"}],
"recording_url": "https://<your-intellismi>/api/v1/calls/acc-1-7/recording"
}
If a secret is set, X-Intellismi-Signature: sha256=<hex> is the HMAC-SHA256 of
the raw body. Verify it:
import hmac, hashlib
expected = "sha256=" + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, request.headers["X-Intellismi-Signature"])
Respond with any 2xx within 10 s. Non-2xx and timeouts are retried twice (2 s, then 4 s later); failures are recorded in the audit log.
Contacts
POST /api/v1/contacts{"phone": "+973…", "name": "Ali Hassan", "company": "", "account_ref": "C-1042", "notes": ""}— create or update by phone. The assistant greets known callers by name and can quoteaccount_ref.GET /api/v1/contacts/lookup?phone=+973….
Usage and billing
GET /api/v1/usage?since=&until= — defaults to the current calendar month in
the organisation's timezone.
{
"plan": "Business", "included_seconds": 72000,
"billed_seconds": 4212, "overage_seconds": 0, "price": 0.0, "currency": "BHD",
"by_direction": [{"direction": "inbound", "seconds": 3900, "price": 0, "calls": 61}, {"direction": "outbound", "seconds": 312, "price": 0, "calls": 9}],
"by_day": [{"day": "2026-09-01", "seconds": 640, "price": 0, "calls": 11}, …]
}
How a call is charged:
- Time is measured only while the assistant is on the line. Ringing, welcome messages, IVR menus and time after a transfer are free.
- That time is rounded up to whole seconds (
billed_seconds). - Each second is priced at the rate for the model that handled the call and
the direction (inbound/outbound). Seconds within the plan's
included_secondsfor the month are credited on the statement; the rest is the usage charge.
Reports (CSV)
Signed-in users can download from the dashboard (Home → Reports, Settings → Plan & billing) or directly:
GET /api/reports/calls.csv?from=2026-09-01&to=2026-09-30
GET /api/reports/usage.csv # one row per billed call
GET /api/reports/transcripts.csv
GET /api/reports/messages.csv
GET /api/reports/contacts.csv
Dates are inclusive and interpreted in the organisation's timezone. Files are UTF-8 with BOM, so Arabic opens correctly in Excel.
Real-time events
The dashboard's WebSocket at /ws streams call_state, incoming,
transcript and reg_state events for the signed-in organisation; API keys
can connect with the same Authorization header.
Free AI (self-hosted) {#free-ai}
Intellismi can run on free, open models on a server you control, with no per-second
usage charge. Speech-to-text, replies and the voice each run as an
OpenAI-compatible service; docker-compose.local-ai.yml in the repository
starts all three on one NVIDIA machine:
docker compose -f docker-compose.local-ai.yml up -d
docker exec -it $(docker ps -qf name=ollama) ollama pull qwen2.5:7b # or jais / allam for Arabic
Then in the dashboard: Settings → AI agents → profile → "Use the free AI
stack", save, and assign the profile to a line. Calls handled by this profile
are metered at the local:* rate, which is zero by default. Expect replies to
start about a second after the caller stops speaking; the cloud provider is a
little faster and supports tools (messages, transfers, lookups) - the free
stack does not yet.
Inbox requests
Everything the agent writes down for your team - messages, callbacks, complaints, orders, bookings, sales leads - is a request in the Inbox. Your ticketing or CRM system can read them, create them and comment on them.
| Method | Path | What |
|---|---|---|
| GET | /api/v1/requests?status=open&department=Sales&kind=lead&q= |
list; status = new, read, open (new+read), done |
| POST | /api/v1/requests |
create: {caller_name, callback_number, subject, message, kind, department, priority} |
| PUT | /api/v1/requests/{id} |
change status, department, assigned_to, priority, kind |
| POST | /api/v1/requests/{id}/comments |
{"text": "Called back, booked Monday 10:00"} - an internal reply |
A request looks like:
{"id": 41, "created_at": 1789300071.9, "call_id": "acc-1-7", "source": "agent",
"caller_name": "Ali Hassan", "callback_number": "+97339000000",
"kind": "lead", "department": "Sales", "priority": "high", "status": "new",
"subject": "Quote for 20 chairs", "message": "Wants a quote for 20 office chairs by Thursday",
"assigned_to": null, "comments": []}
Requests taken during a call are also inside that call's webhook under
messages, so a CRM can create its own ticket the moment the call ends.
Outbound campaigns
Call a list of people with a script and get the answers back as a spreadsheet: consent calls, surveys and interviews, sales follow-ups, appointment confirmations for a whole day. Manage them in the dashboard under Call Center → Outbound campaigns, or over the API.
| Method | Path | What |
|---|---|---|
| GET | /api/campaigns/presets |
ready-made scripts: permission, interview, sales, custom |
| POST | /api/v1/campaigns |
create - see the example |
| GET | /api/v1/campaigns |
list with progress counts |
| GET | /api/v1/campaigns/{id} |
progress plus every contact's status, outcome and answers |
| PUT | /api/v1/campaigns/{id} |
change the script, agent, line, window, attempts |
| POST | /api/v1/campaigns/{id}/contacts |
{"rows": [{"phone": "+973…", "name": "Ali", "fields": {"order_no": "A-17"}}]} |
| POST | /api/campaigns/{id}/upload |
multipart CSV / Excel: phone, name, … (template: /api/campaigns/template.csv) |
| POST | /api/v1/campaigns/{id}/status |
{"action": "start" \| "pause" \| "reset"} |
| GET | /api/v1/campaigns/{id}/results.xlsx or .csv |
one row per person, one column per question |
# Create a consent campaign, add people, start it
curl -X POST https://<your-intellismi>/api/v1/campaigns -H "Authorization: Bearer aicc_…" -H "Content-Type: application/json" -d '{
"name": "WhatsApp consent - March", "kind": "permission", "agent_profile_id": 7, "language": "ar",
"intro": "Hello {name}, this is {agent} from {company}. Do you have a minute?",
"questions": [
{"key": "consent", "text": "May we send you our monthly offers on WhatsApp?", "type": "yes_no"},
{"key": "channel", "text": "Would you prefer WhatsApp, SMS or email?", "type": "choice", "options": ["WhatsApp", "SMS", "Email"]}
],
"closing": "Thank you {name}, have a good day.",
"window_start": "09:00", "window_end": "20:00", "max_attempts": 2, "retry_minutes": 60}'
curl -X POST https://<your-intellismi>/api/v1/campaigns/3/contacts -H "Authorization: Bearer aicc_…" -H "Content-Type: application/json" \
-d '{"rows": [{"phone": "+97339000001", "name": "Ali"}, {"phone": "+97339000002", "name": "Sara", "fields": {"order_no": "A-17"}}]}'
curl -X POST https://<your-intellismi>/api/v1/campaigns/3/status -H "Authorization: Bearer aicc_…" -H "Content-Type: application/json" -d '{"action": "start"}'
Question types: text, yes_no, number, choice (with options), date.
{name}, {company}, {agent} and any fields column (e.g. {order_no})
are filled in per person. Calls go out one at a time per campaign inside the
window, retried max_attempts times retry_minutes apart. Outcomes:
completed, partial, declined, callback, wrong_number, no_answer.
Each campaign call is a normal call: it appears in GET /api/v1/calls with
reference = campaign:<campaign id>:<contact id> and fires the webhook.
Integration recipes
Appointment reminders from your booking system. The evening before, for
each appointment POST /api/v1/calls with caller_name, purpose
("Remind … tomorrow at 10:00; confirm, or offer Wednesday 11:00 or
Thursday 09:00"), reference = your appointment id. On the webhook, read
summary and messages; if the transcript contains a new time, update the
booking. The agent can also book directly if you connect your API under
Settings → Integrations.
Order or delivery confirmation. POST /api/v1/calls with the order in
metadata and a purpose like "Confirm the delivery address and a time slot
today between 2 and 6". Both come back untouched in the webhook.
Lead follow-up. When a lead is created in your CRM, add the person to a
running sales campaign with POST /api/v1/campaigns/{id}/contacts. Read
the answers from GET /api/v1/campaigns/{id} or the results file, or catch
each call's webhook (reference starts with campaign:).
Two-way ticketing. Poll GET /api/v1/requests?status=new (or use the
webhook's messages) to create tickets; when your agent resolves one, PUT
/api/v1/requests/{id} with {"status": "done"} and post the resolution as a
comment so the call-center team sees it.
Live dashboard in your own app. Connect to /ws with the same
Authorization header to receive call_state, incoming and transcript
events as they happen.