Place outbound calls from an Aquant agent and read back what happened on them — duration, transcript, and an AI-generated summary — over plain HTTPS and JSON. This is the REST surface that the SIP and PSTN telephony integrations point at once a call has ended.
The Call API is an ordinary HTTPS/JSON interface. You exchange your API credentials for a short-lived bearer token, then use it to make an agent call someone, or to read back what happened on a call that has already ended. Everything is scoped to the one agent your token was issued for.
| Place a call | Have the agent dial a phone number, or every active member of a caller group, and converse with whoever answers. See Placing a call. |
| Read call details | How long the call lasted and the speaker-attributed transcript of it. See Call details. |
| Read the summary | The AI-generated summary of the conversation, produced by post-call analysis. See Call summary. |
| Method | Path | Purpose |
|---|---|---|
POST | /acai/auth | Exchange credentials for a bearer token. |
POST | /acai/auth/revoke | Invalidate a token immediately. |
POST | /acai/call/place | Place an outbound call. |
GET | /acai/call/{call_record_id} | Duration and transcript. |
GET | /acai/call/{call_record_id}/summary | Post-call summary. |
/acp alias. All paths are relative to https://acai-api.aquant.ai. The API is served under /acai; /acp is retained as a permanent alias so existing integrations keep working. New work should use /acai. The two are the same endpoints, and a token issued at one prefix works at the other.
| API key & secret | A credential pair for your organization, issued from the Aquant console. Distinct from the SIP username/password used for SIP digest authentication. |
| Agent id | The id of the agent this integration acts as. A token is bound to exactly one agent, and every endpoint here is scoped to it. |
| A sender id | A short label naming your system — free text, up to 256 characters. It is recorded against the session so activity can be attributed back to the integration that caused it. |
| An agent phone number | Only for placing calls. The agent needs a number provisioned to call out from; without one, call placement is rejected. |
/acai/auth, never on the call endpoints.
Two steps, always. Exchange the long-lived key and secret for a short-lived token, then send that token as a bearer credential on every other call. The key and secret never appear on the call endpoints themselves.
| Field | Type | |
|---|---|---|
api_key | required string | Your organization's API key. |
api_secret | required string | The matching secret. |
agent_id | required string | The agent to bind this session to. |
sender_id | required string | A label identifying your system. Max 256 characters. |
POST https://acai-api.aquant.ai/acai/auth
Content-Type: application/json
{ "api_key": "...",
"api_secret": "...",
"agent_id": "a1f0c8d2-...",
"sender_id": "crm-sync" }
{
"token": "acp_chat_token_9f3b...",
"expires_in": 3600,
"expires_at": "2026-08-28T11:42:07.315Z",
"agent": {
"agent_id": "a1f0c8d2-...",
"agent_name": "Support Agent",
"agent_phone": "+14155550123"
}
}
Send it as a bearer credential on every other endpoint:
Authorization: Bearer acp_chat_token_9f3b...
| Valid for | One hour from issue. |
| Sliding expiry | Every successful authorized call resets the clock to a full hour. A token used regularly stays alive; an idle one expires an hour after its last use. |
| On expiry | Requests return 401. Call /acai/auth again for a new token — there is no refresh flow. |
| Bound to | One agent and one sender_id. To act as a second agent, get a second token. |
POST https://acai-api.aquant.ai/acai/auth/revoke
{ "token": "acp_chat_token_9f3b..." }
# → { "revoked": true }
Idempotent: revoking a token that is missing, already revoked, or already expired returns revoked: false rather than an error, so a cleanup path never has to special-case it.
/acai/auth call is a credential check against the database, and the call endpoints are rate limited per token — so minting a fresh token for each request is both slower and no more generous.
Makes the agent dial out. The agent calls from its own provisioned number and, when the call is answered, converses with whoever picked up exactly as it would on an inbound call.
| Field | Type | |
|---|---|---|
to_number | one of string | A single recipient in E.164 form, e.g. +14155550123. Spaces, dashes and parentheses are stripped, and a missing + is added. |
group_id | one of string | A caller group. Every active member's phone number is dialed. See Calling a group. |
call_params | optional object | Flat key/value pairs handed to the agent for this call, so the conversation can open with context your system already has — a ticket number, a customer name. Values are stringified. |
to_number or group_id. Sending both, or neither, is a 422. There is no way to pass an ad-hoc list of numbers in one request; use a group, or one request per number.
POST https://acai-api.aquant.ai/acai/call/place Authorization: Bearer <token> { "to_number": "+14155550123", "call_params": { "ticket": "INC-4821", "customer": "Acme Corp" } }
{
"success": true,
"call_sid": "CA7f21c0e9b34d...",
"error": null,
"total": 1,
"succeeded": 1,
"failed": 0
}
success: true means the call was placed, not answered. This endpoint is fire-and-forget: it returns as soon as the telephony provider accepts the request. Ringing, no answer, busy, voicemail and the conversation itself all happen afterwards. Nothing about the outcome comes back on this response — read it from call details or the summary once the call has ended.
With group_id, the group's active members are resolved to phone numbers, deduplicated, and dialed concurrently. The response shape changes: a per-target results array replaces the single call_sid.
{ "group_id": "3d81..." }
# →
{
"success": true,
"total": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "to_number": "+14155550123", "success": true, "call_sid": "CA7f21..." },
{ "to_number": "+14155550188", "success": true, "call_sid": "CAb90c..." },
{ "to_number": "+14155550199", "success": false, "error": "..." }
]
}
| Maximum targets | 10 per request. Each target is a full AI conversation with live audio, so calls are capped far harder than SMS or email. A larger group is rejected rather than truncated. |
| Active members only | Inactive members are skipped silently and are not counted in total. |
| Partial success | success is true if at least one target was placed. Always check failed and the per-target results — a 200 does not mean every call went out. |
| Rate-limit cost | One unit per target, not per request — see Rate limits. |
| Empty or inactive group | 400, and nothing is dialed. A group id that doesn't exist in your organization is 404. |
429 — nothing is dialed. Keep groups at 5 or fewer, or split them across minutes.
429 as back-pressure to respect, not an error to retry immediately, and don't retry a request whose outcome you don't know — you have no way to tell a failed placement from a placement whose response you lost.
How long a call lasted and what was said on it. Both are produced after the call ends, so this endpoint is designed to be polled: the status field tells you whether to poll again or stop.
GET https://acai-api.aquant.ai/acai/call/6cbd7ac0-33ab-4790-9c3c-bdfc64a4e079
Authorization: Bearer <token>
{
"call_record_id": "6cbd7ac0-33ab-4790-9c3c-bdfc64a4e079",
"status": "Successful",
"duration_s": 184,
"transcript": "Caller: Hi, I'm calling about...\nAgent: Of course — can you..."
}
| Field | Type | |
|---|---|---|
call_record_id | string | Echoed back, so a response can be matched to its request in a batch. |
status | enum | Successful, Pending, or Error. Describes the whole payload, not the transcript alone. |
duration_s | int · nullable | Call length in whole seconds. Best-effort — see below. |
transcript | string · nullable | Speaker-attributed plain text, one Speaker: line per row, in conversation order. |
| Status | Meaning | What to do |
|---|---|---|
Successful | Everything this endpoint can give you for that call is in the response. | Consume it. Stop polling. |
Pending | The call is still in progress, or it has ended and the transcript is still being produced. | Poll again shortly. |
Error | The details are permanently incomplete. Transcription failed, or it reported finishing but produced nothing usable. | Stop polling. Nothing further will arrive. |
Successful with a null transcript means the call was not recorded. That is a normal, healthy answer, not a failure: recording is a per-agent setting, and when it is off no transcript was ever expected. This is exactly why you should branch on status rather than on whether transcript is present — a null transcript alone cannot tell you whether to wait, give up, or accept the result.
status is decidedIncluded so polling logic can be written against the real behaviour rather than inferred from observation.
| Situation | Status |
|---|---|
| Call is still in progress | Pending |
| Call ended; recording was off for the agent, so nothing was captured | Successful (transcript null) |
| Call ended; transcription is queued or running | Pending |
| Transcription finished and the transcript was read | Successful |
| Transcription finished but the transcript is missing or empty | Error |
| Transcription finished but could not be read right now | Pending |
| Transcription failed | Error |
duration_s can be null on a perfectly successful call, and never affects status. Duration is written by the telephony provider's end-of-call notification; a call that ends by the audio stream stopping or the far end dropping can complete normally without one being recorded. Treat it as best-effort metadata: use it when present, don't infer failure from its absence, and don't use it to decide whether a call happened. It is returned whenever it is known, including alongside Pending and Error.
Pending for its whole duration plus transcription time, so back off rather than hammering. A call whose end was never registered — a rare infrastructure failure mid-call — stops reporting Pending after six hours, so a poller cannot be stuck on one record forever.
The AI-generated narrative summary of the conversation, produced by post-call analysis. Separate from call details because it comes from a later stage of the pipeline and has its own readiness vocabulary.
GET https://acai-api.aquant.ai/acai/call/6cbd7ac0-.../summary Authorization: Bearer <token> # → { "call_record_id": "6cbd7ac0-33ab-4790-9c3c-bdfc64a4e079", "status": "Ready", "summary": "The caller reached out for assistance regarding..." }
| Status | Meaning | What to do |
|---|---|---|
Ready | Summary is available in summary. | Consume it. |
Pending | Analysis is still running, or the text is mid-write. | Poll again shortly. |
Failed | Analysis ran and failed. | Stop polling; no summary will arrive. |
Unavailable | Analysis was never requested for this call — recording or analysis is off on the agent. | Stop polling; enable the feature on the agent if you need summaries. |
Successful / Pending / Error. The summary answers "is the analysis stage done?" with Ready / Pending / Failed / Unavailable. Don't write one status handler for both — in particular, an unrecorded call is Successful on details but Unavailable on the summary, because there was nothing to summarize.
Both read endpoints are addressed by call_record_id, the platform's own identifier for a call. This is not the same value as the telephony provider's call_sid, and knowing where each one comes from saves a lot of confusion.
call_record_id | Aquant's identifier for the call. A UUID. The value the details and summary endpoints expect in the path. |
call_sid | The telephony provider's identifier, returned by call placement. Useful for correlating with your carrier's records. Not accepted by the read endpoints. |
| Source | |
|---|---|
| SIP handover | A handed-over leg carries the call id in a User-to-User header as hex-encoded JSON; decode it to UTF-8, parse the JSON, and read the uri field. See the SIP reference. This is the primary programmatic source. |
| Console call history | Every call is listed with its id, for looking one up by hand or during development. |
call_record_id. /acai/call/place returns the provider's call_sid, and there is currently no endpoint that translates a call_sid into a call_record_id, nor one that lists an agent's recent calls. So an integration that places a call cannot yet fetch that same call's details on its own. If your use case needs this, raise it with your Aquant contact — it is a recognised limitation rather than a documented workflow.
404 — the same answer as an id that doesn't exist, deliberately, so the endpoint can't be used to discover which ids are real. Use the token belonging to the agent that handled the call.
Two limits apply to every request: a ceiling across the whole token, and a tighter per-endpoint budget. Both are per-token, per-minute. Exceeding either returns 429.
| Scope | Limit | |
|---|---|---|
| Any endpoint, per token | 30 / min | The overall ceiling. Every authorized request counts. |
POST /acai/call/place | 5 / min | Calls cost real money, so they are capped hard. A group consumes one unit per target. |
GET /acai/call/{id} | 10 / min | Each request reads the transcript from storage and can return a whole call's text. |
GET /acai/call/{id}/summary | 20 / min | Cheaper and text-only, so more generous — but still below the token ceiling, so polling can't starve your sends. |
404 is not free.
Standard HTTP status codes. The body carries a detail string describing what went wrong.
| Code | When |
|---|---|
400 | A well-formed request that can't be carried out: recipient number isn't valid E.164, the agent has no phone number to call from, the caller group is inactive, or the group resolved to no active members. |
401 | Bad API key or secret on /acai/auth; or a missing, malformed, expired, or revoked bearer token on any other endpoint. Re-authenticate. |
404 | The call id doesn't exist or belongs to a different agent than the token's — the two are indistinguishable by design. Also returned for a caller group that isn't in your organization. |
422 | The body failed schema validation: a required field is missing, or the exactly-one-of rule between to_number and group_id was broken by supplying both or neither. |
429 | A rate limit was exceeded. Back off; limits reset on a one-minute boundary. |
Authenticate, place a call, then poll for the transcript once it has ended.
curl -X POST https://acai-api.aquant.ai/acai/auth \ -H 'Content-Type: application/json' \ -d '{"api_key":"...","api_secret":"...", "agent_id":"a1f0c8d2-...","sender_id":"crm-sync"}' # → { "token": "acp_chat_token_9f3b...", "expires_in": 3600, ... }
curl -X POST https://acai-api.aquant.ai/acai/call/place \ -H 'Authorization: Bearer acp_chat_token_9f3b...' \ -H 'Content-Type: application/json' \ -d '{"to_number":"+14155550123", "call_params":{"ticket":"INC-4821"}}' # → { "success": true, "call_sid": "CA7f21...", ... } # The call is now ringing. Nothing more arrives on this response.
Using a call_record_id obtained as described in Finding a call id.
curl https://acai-api.aquant.ai/acai/call/6cbd7ac0-... \ -H 'Authorization: Bearer acp_chat_token_9f3b...' # → { "status": "Pending", "duration_s": null, "transcript": null } poll again # → { "status": "Successful", "duration_s": 184, "transcript": "Caller: ..." } done
# pseudocode while true: r = get(f"/acai/call/{call_record_id}") if r.status == "Successful": return r.transcript # may be null: not recorded if r.status == "Error": return None # permanent; stop sleep(5) # Pending; 10/min budget
What changed in this revision of the reference. Behaviour that was already live but undocumented is marked clarified.
| Date | Change |
|---|---|
| 28 Aug 2026 |
Initial reference. The Call API gathered into one page: session authentication and revocation, outbound call placement including group fan-out, call details, and the post-call summary. Call details endpoint. New GET /acai/call/{id} returning duration and the speaker-attributed transcript, with a Successful / Pending / Error readiness status covering the whole payload.Status derivation published. The full situation-to-status table for call details, so polling logic can be written against the actual behaviour. Rate limits published. The token-wide ceiling and all per-endpoint budgets, including the per-target cost of a group fan-out. Clarified: duration_s is best-effort and may be null on a successful call; Successful with a null transcript means the call was not recorded; the details and summary endpoints use different status vocabularies deliberately; a foreign call id returns 404 rather than 403 so ids cannot be enumerated; a rate-limit unit is charged before the ownership check; a retired agent's call history remains readable; calls placed through the API do not currently yield a call_record_id.
|