Calls and campaigns
Place a call
POST /v1/calls
Authorization: Bearer alt_live_…
Idempotency-Key: invoice-42
{
"agent": "7d1c…",
"to": "+393331234567",
"inputs": { "name": "Luca Galli", "amount": 250, "month": "January" },
"reference": "invoice-42",
"scheduled_at": "2026-10-01T09:30:00+02:00",
"callback_url": "https://your-app.example/allerto/outcomes"
}
| Field | Required | Meaning |
|---|---|---|
agent | yes | A published outbound agent |
to | yes | E.164 number |
inputs | when the agent requires them | Validated against the agent's inputs |
reference | no | Your id; returned in every event |
scheduled_at | no | Earliest time for the call |
callback_url | no | HTTPS URL receiving this call's events instead of the agent's webhook |
if_blocked | no | reject (default) or queue: see When a call cannot start now |
The response is 202 with the call object, and a call for now is dialled within seconds. When it cannot start now (outside the calling window, or the number already had its calls for the day) the request is refused with 409: see When a call cannot start now. Repeating the request with the same Idempotency-Key returns 200 and the same call: it is never dialled twice.
What happens next
- The queue waits for
scheduled_atand for the agent's calling window (Italian time). - It checks the do-not-call list, the limit per number (3 calls in 24 hours across the account, up to 10 with the agent's
calling.maxCallsPerNumber) and the account's concurrent calls (3 by default). - The phone rings from the agent's caller ID, once, and the agent speaks as soon as the person answers. Answering machines are not detected: the agent talks to them as to a person.
- No answer or busy: the call ends there and
call.completedis sent with that outcome. Nothing is redialled automatically: to try again, create a new call. - After a conversation the transcript is turned into the agent's
result, the outcome is classified andcall.completedis sent. do_not_calladds the number to your list.callback_requestedwith a time creates a new call at that time (inside the window) whencalling.autoCallbackis on; it links back withretry_of.
While a call is queued, next_attempt_at says when it will be dialled and waiting_reason why it waits:
waiting_reason | Meaning |
|---|---|
outside_hours | Outside the agent's calling window |
daily_limit | The number already had its calls for the last 24 hours; it starts when a slot frees up |
callback | The person asked to be called back at that time |
scheduled | The scheduled_at chosen at creation |
Cancel a call that has not started with POST /v1/calls/{id}/cancel.
When a call cannot start now
Two platform rules can hold a call: the agent's calling window and the limit of calls to the same number in 24 hours (3 by default, up to 10 with calling.maxCallsPerNumber, counting every agent of the account). What happens then depends on if_blocked:
reject(default for a call withoutscheduled_at): the request is refused at once with409anddetails.retry_after, the earliest time it could start. Nothing is queued: send the request again from then, or to another number.queue(default whenscheduled_atis set): the call is accepted and waits;waiting_reasonandnext_attempt_atsay why and until when.
When testing on your own phone, mind the limit: after 3 calls to the same number in 24 hours (or the agent's calling.maxCallsPerNumber) the next ones are refused with daily_limit.
{
"error": {
"code": "daily_limit",
"message": "This number has had 3 calls in the last 24 hours: retry from 2026-10-09 15:20 (Italian time)",
"details": { "retry_after": "2026-10-09T13:20:35.000Z" }
}
}
The status is 409; the code is outside_hours or daily_limit, and details.retry_after is the first moment the call could start. Test keys skip both checks, as the queue does for test calls.
The call object
{
"id": "…",
"object": "call",
"direction": "outbound",
"livemode": true,
"agent": { "id": "…", "version": 3, "name": "Payment reminder" },
"client": { "id": "…", "external_id": "default" },
"campaign_id": null,
"reference": "invoice-42",
"to": "+393331234567",
"from": "+390280781047",
"inputs": { "name": "Luca Galli", "amount": 250, "month": "January" },
"status": "completed",
"outcome": "completed",
"attempts": 1,
"max_attempts": 1,
"next_attempt_at": null,
"waiting_reason": null,
"answered_at": "…",
"ended_at": "…",
"duration_seconds": 94,
"callback_at": null,
"transfer": null,
"tool_calls": [],
"ai_disclosed": true,
"recording": null,
"anonymized_at": null,
"result": {
"data": { "answer": "will_pay", "pay_by": "2026-10-05" },
"issues": [],
"summary": "…",
"sentiment": {
"label": "negative",
"score": -1,
"start": -2,
"end": 0,
"trend": "improving",
"emotions": [
{
"type": "frustration",
"intensity": "high",
"evidence": "it's the third time I've called"
}
]
},
"quality": {
"score": 6,
"tags": ["repeated_question"],
"reason": "The agent asked for the address again after the person had given it."
}
},
"retry_of": null,
"error": null,
"created_at": "…"
}
Inbound calls have the same shape with direction: "inbound", from set to the caller and no inputs.
Status: queued → dialing → in_progress → processing → completed; or failed, cancelled; needs_review while an operator checks the data (inbound agents with missing data).
Outcome:
| Outcome | Answered | Retried | Meaning |
|---|---|---|---|
completed | yes | no | The conversation reached its purpose (whatever the answer) |
callback_requested | yes | automatic, see callback_at | Asked to be called later |
wrong_person | yes | no | Someone else answered |
refused | yes | no | Did not want to talk |
do_not_call | yes | never | Asked not to be called again |
hung_up | yes | no | Ended before saying anything useful |
transferred | yes | no | Handed to the operator of the agent's transfer settings |
no_answer, busy | no | no | |
voicemail | no | no | Answering machine (test mode only) |
suppressed | no | no | Number on the do-not-call list |
invalid_number, failed, cancelled | no | no |
Transfer
A call handed to an operator has outcome: "transferred" and:
"transfer": {
"to": "+390212345678",
"status": "answered",
"requested_at": "…",
"ended_at": "…",
"duration_seconds": 184
}
status is requested or dialing while the operator's phone rings, then answered, no_answer, busy or failed; duration_seconds is the time spent with the operator. call.completed is sent as soon as the data collected before the transfer is ready, usually while the transfer is still going on: read the call for its final status. The conversation with the operator is not transcribed.
Sentiment
Every call where the person spoke comes with result.sentiment, for every agent and without configuration, like the summary.
scoregoes from-2(very negative) to2(very positive);0is a calm, factual call, the usual case.labelisnegative,neutralorpositive.startandenduse the same scale for the first and last part of the call;trendsays whether it wasimproving,stableorworsening.emotionslists up to three emotions the person expressed (frustration,anger,anxiety,confusion,satisfaction,gratitude), each with anintensity(low,medium,high) and the person's own words asevidence. An emotion is kept only if those words are really in the transcript.
It is read from what the person said, never from their voice: it is text analysis, not emotion recognition from biometric data. sentiment is null when the person did not speak or the call could not be analysed; test-mode calls carry a neutral sentiment.
Quality
Every call where the person spoke also comes with result.quality: how well the agent handled it, read from the transcript after the call, for every agent and without configuration. It never changes the call.
scoregoes from1(the call failed because of the agent) to10(smooth and complete).tagslists the problems the transcript clearly shows, from a fixed set that is the same for every sector:repeated_question(asked again for something already said),loop(repeated itself or got stuck),not_understood(asked to repeat more than once),wrong_language,improper_reply(off the point, invented or promised something),asked_for_person,hearing_problem(the person could not hear, bad line),ended_early(the call ended before the request was understood).reasonis one sentence on the main problem. When the call is anonymised, the score and tags stay and the reason goes.
Counting tags across calls shows what to fix in an agent's instructions without listening to the calls.
Reading calls
GET /v1/calls?direction=outbound&outcome=callback_requested&since=2026-09-01T00:00:00Z&limit=50
GET /v1/calls/{id}
GET /v1/calls/{id}/transcript
GET /v1/calls/{id}/recording
ai_disclosed says whether the agent stated it is an AI in its first words (null when it never spoke). recording is { "url": "/v1/calls/{id}/recording" } when the agent records calls: a mono 8 kHz WAV. After 24 months personal data is removed and anonymized_at is set; see Privacy.
Each turn of the transcript has role (agent or person), text and at_ms, when it started in milliseconds from the start of the call. It is null on calls made before it was measured. tool_calls on the call carry their own at, so the two can be read as one timeline.
Lists are newest first; pass next_cursor as cursor for the next page. Filters: direction, agent, campaign, status, outcome, reference, since.
Campaigns
POST /v1/campaigns
{
"agent": "7d1c…",
"name": "January instalments",
"contacts": [
{ "to": "+393331234567", "inputs": { "name": "Luca Galli", "amount": 250, "month": "January" }, "reference": "A-1" },
{ "to": "+393339876543", "inputs": { "name": "Anna Verdi", "amount": 90, "month": "January" }, "reference": "A-2" }
],
"max_concurrent": 3
}
Every contact is validated before anything is queued: if one is invalid you get 400 invalid_contacts with the index of each problem, and nothing is queued. Duplicates and numbers on the do-not-call list are skipped and reported in skipped. Up to 5,000 contacts per request.
allerto campaigns create --agent <id> --csv contacts.csv --name "January instalments"
The CSV needs a to column (also phone, telefono, numero); reference is optional; every other column is an input. Commas or semicolons both work, and Italian numbers like 333 1234567 become +393331234567.
GET /v1/campaigns/{id} returns counts by_status and by_outcome. pause, resume and cancel are POST /v1/campaigns/{id}/<action>; cancelling cancels the calls still queued.
Do-not-call list
GET /v1/suppressions
POST /v1/suppressions {"number": "+393331234567", "reason": "Asked by email"}
DELETE /v1/suppressions/%2B393331234567
The list is checked when a call is queued and again right before dialling. Adding a number fails its queued calls with suppressed.