REST API · reference
Endpoints and responses
Base URL: http://127.0.0.1:8377. Every identifier is a UUID issued by the softphone: a call id comes from /calls or from the response to POST /calls, an account id from /accounts. The endpoints below require no token; write endpoints appear on the index page once a token is sent.
Field names are snake_case, and the suffix gives the type: _id is a reference to a UUID, _ts is an instant in Unix milliseconds (UTC), _s is a duration in seconds. This applies to the REST API and to webhooks alike; /settings keeps its own names.
Updating an older integration. Earlier versions used camelCase and short identifiers. accountId is now account_id, startedAt is callstart_ts, durationSeconds is duration_s, answeredBy is answered_by, and the webhook field at is event_ts. Calls and accounts are identified by UUID only: runtimeId and identifiers such as call-3 or account-2 are no longer returned or accepted.
| Method | Endpoint | Description |
| GET | /metrics | Metrics in Prometheus format |
| GET | /ui | Recordings list as an HTML page |
| GET | /ui/recordings/{id} | Recording with its transcript as an HTML page |
| GET | /ui/recordings/{id}/audio | Recording audio for the page above |
| GET | /contacts | Contact list |
| GET | /contacts/{id} | A single contact |
| GET | /history | Call history, newest first. Supports ?limit=, ?missed=true and ?declined=true |
| GET | /calls | Active calls |
| POST | /calls | Start a call: {number, account_id?} |
| POST | /calls/{id}/answer | Answer the call |
| POST | /calls/{id}/hangup | Hang up the call |
| POST | /calls/{id}/hold | Put the call on hold |
| POST | /calls/{id}/resume | Resume a held call |
| POST | /calls/{id}/dtmf | Send DTMF digits: {digits} |
| POST | /calls/{id}/transfer | Transfer the call: {target} |
| GET | /accounts | SIP accounts and registration status. Passwords are never returned |
| GET | /settings | Full configuration, excluding secrets |
| GET | /taxonomy | Categories, tags and red flags with their codes |
Active calls
The calls in progress on this workstation and the state of each. Use it for a wallboard, or to read the full picture after a webhook arrives.
GET /calls200 · application/json
[
{
"id": "7620f1c5-fa8e-4453-80bf-4274f5324bec",
"seance_id": "0b6f1d2e-5c3a-4f8e-9a71-2d4c6e8f0a13",
"account_id": "aa24a2c4-709b-402a-ac58-7ca6788dcb94",
"direction": "in",
"state": "hold",
"number": "+15551234567",
"name": "Jane Miller",
"uri": "sip:+15551234567@pbx.example.com",
"dialed": "",
"muted": false,
"event_ts": 1788788942412,
"callstart_ts": 1788788890398,
"callstate_ts": 1788788911207
}
]
| Field | Description |
id | The call’s UUID: the {id} in /calls/{id}/… and the id in webhooks. It stays the same for the whole call. |
seance_id | The conversation the call belongs to. Calls linked by a transfer, a consultation or a conference share the same seance_id. |
account_id | The line the call is on: an id from /accounts. |
state | dialing, ringing-out, ringing-in, active, hold (held by this softphone), onhold (held by the other party), conference or ended. When more than one applies, conference takes precedence over hold, and hold over onhold. |
muted | Whether the microphone is muted on this call. Muting does not change state. |
callstart_ts / callstate_ts | When the softphone first learned of the call, and when the call entered its current state. null if not known. |
event_ts | When this response was generated. Compare it with callstate_ts to see how long the call has been in its current state without relying on your own clock. |
Starting a call
The only write endpoint most integrations need. account_id is optional; if omitted, the call is placed on the line currently selected in the dialer.
POST /callsrequest
{ "number": "+15551234567", "account_id": "aa24a2c4-709b-402a-ac58-7ca6788dcb94" }
number is required. Without it, the API returns 400 with {"error":"a call needs a number"} and no call is placed.
The response contains the new call’s UUID. Use it in /calls/{id}/…; webhooks for this call carry the same id. The number is completed on the chosen account the way the dialer completes it: 1001 is sent as sip:1001@pbx.example.com.
POST /calls200 · application/json
{ "id": "7620f1c5-fa8e-4453-80bf-4274f5324bec" }
POST /calls/{id}/transfer completes target the same way, on the account the call is on. A target that already contains a scheme or an @ is sent unchanged.
Accounts
Configured lines, their settings and current PBX registration status. Passwords are never included in the response and cannot be retrieved through the API.
GET /accounts200 · application/json
[
{
"id": "aa24a2c4-709b-402a-ac58-7ca6788dcb94",
"display_name": "1001 Reception",
"username": "1001",
"auth_user": "",
"domain": "pbx.example.com",
"registrar": "",
"outbound_proxy": "pbx.example.com:9061",
"port": 9061,
"transport": "udp",
"verify_certificate": true,
"media_encryption": "off",
"expiry_s": 300,
"enabled": true,
"dtmf": "rfc2833",
"auto_answer": false,
"auto_answer_after_s": 0,
"address": "1001@pbx.example.com",
"state": "registered"
}
]
| Field | Description |
id | Persistent account UUID — the only identifier an account has in the API. It is the account_id in calls, call history, contacts and webhooks, and the {id} in PUT /accounts/{id}. |
transport / media_encryption / verify_certificate | SIP transport: udp, tcp or tls. media_encryption is off, offered or required; verify_certificate controls whether the PBX certificate is checked on a TLS connection. |
state | Current registration state on the PBX: registered while the line is active. |
enabled | Whether the line is enabled, regardless of its registration state. |
Call history
Newest first, 100 entries unless ?limit= says otherwise. ?missed=true returns missed calls only, ?declined=true only the calls this softphone declined.
GET /history?limit=2200 · application/json
[
{
"id": "5d0c9e4a-2b7f-4c1e-8a3d-6f9b0e1c2d34",
"seance_id": "9a3e7c15-4d2b-4e8f-b6a0-3f1c5d7e9b28",
"callstart_ts": 1788780041733,
"direction": "in",
"outcome": "missed",
"answered": false,
"duration_s": 0,
"number": "1012",
"name": "Jane Miller",
"uri": "sip:1012@pbx.example.com",
"dialed": "",
"account": "1001@pbx.example.com",
"account_id": "aa24a2c4-709b-402a-ac58-7ca6788dcb94",
"reason": "cancelled",
"answered_by": "no"
},
{
"id": "5217a034-c801-4aa2-ad93-7d3b42753512",
"seance_id": "c4e2a9d0-7b31-4f6a-9e05-1d8c3b7a6f52",
"callstart_ts": 1788782704116,
"direction": "out",
"outcome": "answered",
"answered": true,
"duration_s": 20,
"number": "506",
"name": "Warehouse",
"uri": "sip:506@pbx.example.com",
"dialed": "506",
"account": "500@pbx.example.com",
"account_id": "380750e3-acd4-4cb4-abe4-cba347781fe7",
"reason": "local-hangup",
"answered_by": "no"
}
]
| Field | Description |
id | The history entry’s own UUID. It is not the call id used by /calls and webhooks; seance_id links the two. |
seance_id | The conversation the call belongs to. Calls linked by a transfer, a consultation or a conference share the same seance_id. |
direction | in or out. |
number / uri | Remote party as a number and as the actual SIP URI. |
name | Resolved from contacts when the number is known; otherwise an empty string. Do not match on this field — use number. |
dialed | Digits dialled for an outgoing call. Empty for incoming calls. |
account / account_id | The line used for the call, as an address and as the UUID from /accounts. |
callstart_ts | When the call started, in Unix milliseconds (UTC). Convert to local time on your side. |
duration_s | Zero for calls that were never connected. |
outcome | Primary classification field: answered, missed, declined or failed. |
reason | Detailed end reason: local-hangup, remote-hangup, cancelled, … |
answered_by | no if answered by a person; otherwise what answered the call. |
Contacts
GET /contacts200 · application/json
[
{
"id": "460dd76e-b139-4808-8df2-7f597faeffea",
"name": "Jane Miller",
"number": "+15551234567",
"account_id": "d51934c2-952b-4dc1-847e-a007de5677ec",
"account": "102@pbx.example.com"
}
]
A contact can be linked to a specific line or to none; an empty account means the contact is not line-specific. Requesting an unknown id returns 404 {"error":"no contact with that id"}.
Taxonomy
The classification used for call summaries: categories, tags and red flags. Each entry has a stable code for matching, a title and description in the interface language, a kind and, for red flags, a severity. Load it once at startup to map the softphone vocabulary to your own fields instead of hard-coding strings.
GET /taxonomy200 · application/json
[
{ "code": "sales", "kind": "category", "title": "Sales", "retired": false, "description": "…" },
{ "code": "support", "kind": "category", "title": "Support", "retired": false, "description": "…" },
{ "code": "callback", "kind": "tag", "title": "Call promised","retired": false, "description": "…" },
{ "code": "churn-risk", "kind": "red_flag", "title": "Risk of leaving", "severity": "high", "retired": false },
{ "code": "sensitive-data","kind": "red_flag","title": "Sensitive data", "severity": "high", "retired": false }
]
Match on code, never on title. Titles are returned in the interface language the softphone is set to — a workstation in Warsaw returns them in Polish. retired: true marks an entry kept so that older calls still resolve; it is no longer assigned to new calls. Keep resolving it, but do not offer it.
Settings (read-only)
GET /settings returns the full configuration except secrets: audio devices and volume levels, codec priority, appearance and language, startup behaviour, hotkeys, diagnostics level and the state of both integrations. Useful for support tools that need to check a workstation’s configuration without screen sharing.
GET /settings200 · abridged
{
"api": { "enabled": true, "port": 8377, "disabled": [] },
"webhooks": { "enabled": true, "method": "POST", "url": "https://crm.example.com/webhooks",
"headerName": "Authorization", "urlTemplate": "", "silenced": [] },
"calls": { "dialAccount": "account-4", "redialIntervalSeconds": 30, "redialGiveUpMinutes": 30,
"codecs": [ { "id": "PCMA/8000/1", "enabled": true }, … ] },
"appearance":{ "language": "en", "theme": "default", "callNotice": "banner", … },
"audio": { "ringer": "Speakers", "ringtone": "bell", "volumes": { "ring": 96, … } },
"startup": { "autostart": true, "closeToTray": true, "startMinimised": true },
"updates": { "automatic": true },
"diagnostics":{ "level": "trace", "systemLog": false }
}
api.disabled lists disabled access groups; webhooks.silenced lists disabled events. Empty lists mean everything is enabled. The response does not include the SIP password, the API token or the webhook header value.
Errors
Every error response is JSON with a single error key. The message is intended for humans, not for parsing.
| Status | Body | Meaning |
404 | {"error":"no such endpoint"} | The path does not exist, or its access group is disabled; both cases return the same response by design. |
404 | {"error":"no contact with that id"} | The path is valid, but the identifier is not. |
400 | {"error":"no call with that id"} | The call has already ended or does not exist. |
400 | {"error":"a call needs a number"} | POST /calls without a number. No call was placed. |
400 | {"error":"no digits to send"} | POST /calls/{id}/dtmf without digits. |
400 | {"error":"a transfer needs a target"} | POST /calls/{id}/transfer without target. |
400 | {"error":"the account this call is on is no longer set up"} | The call’s account was removed while the call was in progress, so the transfer target cannot be completed. Nothing was sent to the PBX. |
Rejected requests are counted, so an integration that fails silently is visible in api_requests_refused_total, not only in your own logs.
Metrics
GET /metrics returns all counters maintained by the softphone, each with a help text. Scrape it with Prometheus or read it directly when troubleshooting.
| Counter | Description |
calls_incoming_total | Incoming calls received |
calls_outgoing_total | Outgoing calls placed |
calls_answered_total | Calls that were answered |
calls_missed_total | Incoming calls that were not answered |
calls_declined_total | Calls rejected locally or by the remote party |
calls_failed_total | Calls that could not be established |
registrations_succeeded_total | Successful SIP registrations |
registrations_failed_total | SIP registrations rejected or timed out |
webhooks_delivered_total | Webhook deliveries accepted by the receiver |
webhooks_failed_total | Webhook deliveries rejected or not delivered |
webhooks_dropped_total | Webhooks discarded because the queue was full, usually because the receiver stopped responding |
api_requests_total | Requests handled by the local control API |
api_requests_refused_total | Rejected requests: invalid token, disabled access group or unknown path |