Allerto API

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"
}
FieldRequiredMeaning
agentyesA published outbound agent
toyesE.164 number
inputswhen the agent requires themValidated against the agent's inputs
referencenoYour id; returned in every event
scheduled_atnoEarliest time for the call
callback_urlnoHTTPS URL receiving this call's events instead of the agent's webhook
if_blockednoreject (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

  1. The queue waits for scheduled_at and for the agent's calling window (Italian time).
  2. 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).
  3. 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.
  4. No answer or busy: the call ends there and call.completed is sent with that outcome. Nothing is redialled automatically: to try again, create a new call.
  5. After a conversation the transcript is turned into the agent's result, the outcome is classified and call.completed is sent.
  6. do_not_call adds the number to your list. callback_requested with a time creates a new call at that time (inside the window) when calling.autoCallback is on; it links back with retry_of.

While a call is queued, next_attempt_at says when it will be dialled and waiting_reason why it waits:

waiting_reasonMeaning
outside_hoursOutside the agent's calling window
daily_limitThe number already had its calls for the last 24 hours; it starts when a slot frees up
callbackThe person asked to be called back at that time
scheduledThe 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:

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:

OutcomeAnsweredRetriedMeaning
completedyesnoThe conversation reached its purpose (whatever the answer)
callback_requestedyesautomatic, see callback_atAsked to be called later
wrong_personyesnoSomeone else answered
refusedyesnoDid not want to talk
do_not_callyesneverAsked not to be called again
hung_upyesnoEnded before saying anything useful
transferredyesnoHanded to the operator of the agent's transfer settings
no_answer, busynono
voicemailnonoAnswering machine (test mode only)
suppressednonoNumber on the do-not-call list
invalid_number, failed, cancellednono

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.

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.

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.