Calls

Place outbound calls, follow their lifecycle, read outcomes and transcripts, stream recordings and hand live calls to a person.
View as Markdown

A call is one phone conversation between one of your agents and one phone number. Jelliu creates a call record for every outbound call it places (from POST /api/calls, a campaign or the MCP server) and for every inbound call that reaches one of your numbers. The record follows the call from the moment it is queued until it ends, and then collects what happened on it: status, outcome, duration, summary, transcript, structured analysis and cost telemetry.

This page covers placing a call, the checks every outbound call must pass, the status lifecycle, outcomes, durations and time limits, recordings, live transfers, hanging up, and how inbound calls are routed.

How it works

Every outbound call, whether you place it directly or a campaign dials it, goes through the same sequence of checks before the phone rings. The first check that fails stops the call and returns an error; no call record is created for checks that run before the record is written.

After the provider accepts the call, the rest of the lifecycle is asynchronous. When the conversation ends, the voice provider reports the transcript and analysis to Jelliu, which closes the record, computes the outcome and sends your webhooks. A background reconciler runs every minute and closes any call whose end was not reported, so a record never stays open forever.

The response to POST /api/calls means the provider accepted the call, not that anyone answered. Track the result with GET /api/calls/{callId} or, better, subscribe to call.completed and call.failed.

The call object

GET /api/calls and GET /api/calls/{callId} return the same shape. The list omits the heavier fields marked detail only. POST /api/calls returns the raw record right after dialing, without the computed fields analysis, has_recording, agent_name, campaign_name and contact_name.

Internal provider identifiers (the carrier call SID and the voice provider’s conversation id) are never returned.

Identity and routing

FieldTypeNullableDescription
idstring (uuid)NoCall id.
tenant_idstring (uuid)NoYour workspace id.
agent_idstring (uuid)YesThe agent that handled the call. Null on very old records.
agent_namestringYesThe agent’s name. Not included in the POST response.
campaign_idstring (uuid)YesThe campaign the call belongs to. Null for standalone and inbound calls.
campaign_namestringYesThe campaign’s name. Not included in the POST response.
contact_idstring (uuid)YesThe contact the call is filed under. See Contacts.
contact_namestringYesThe contact’s name. Not included in the POST response.
phone_numberstringNoThe other party in E.164: the number dialed on outbound calls, the caller on inbound calls.
call_directionstringNooutbound or inbound.
prompt_variant_idstring (uuid)YesThe A/B prompt variant used, if any.

Lifecycle

FieldTypeNullableDescription
statusstringNoOne of queued, ringing, in-progress, completed, failed, no-answer, busy, canceled. See Status lifecycle.
outcomestringYesWhat the call achieved, for example appointment_booked. Null until analysis is available. See Outcomes.
duration_secondsintegerYesLength of the conversation in whole seconds. Null until reported.
started_atstring (date-time)YesWhen the conversation started.
ended_atstring (date-time)YesWhen the record was closed.
termination_reasonstringYesWhy the call ended, as reported by the voice provider (free text). For calls that never connected it starts with initiation_failure:, for example initiation_failure:busy.
has_recordingbooleanNotrue when audio can be requested from /audio. Not included in the POST response.
created_atstring (date-time)NoWhen the record was created. Lists are ordered by this field, newest first.
updated_atstring (date-time)NoLast change to the record.
deleted_atstring (date-time)YesAlways null in API responses: deleted calls are not returned.

Analysis

FieldTypeNullableDescription
summarystringYesShort summary of the conversation. Often arrives a little after the call ends.
sentiment_scorenumberYesAverage caller sentiment from -1 to 1. Null means not measured, which is different from 0.
sentiment_lownumberYesThe most negative caller turn, -1 to 1.
frustration_scorenumberYes0 to 1. Null means not measured; 0 means nobody was frustrated.
call_success_scorenumberYesThe provider’s 0 to 100 confidence that the call met its goal.
objectionsstring[]YesObjections raised by the other party.
evaluation_criteria_resultsobjectNoMap of criterion id to { result, rationale }, where result is success, failure or unknown. {} when none.
data_collection_resultsobjectNoMap of field id to { value, rationale } with the data the agent collected. {} when none.
analysisobjectYesConvenience view built from the fields above. Null when the call produced no analysis signal at all. Not included in the POST response.
analysis.summarystringNosummary, or an empty string.
analysis.outcomestringYesSame as outcome.
analysis.sentimentstringYespositive (score above 0.2), negative (below -0.2), neutral, or null when there is no score.
analysis.sentiment_scorenumberYesSame as sentiment_score.
analysis.key_topicsstring[]NoAlways an empty array today.
analysis.qualificationobjectYesLead qualification derived from collected data: score (0 to 100 or null), interest (high, medium, low or null), answered (boolean or null), next_step, objections, reasons. Null when there is nothing to qualify.
kpi_dataobjectYesDetail only. KPI values computed for the call’s campaign category.
transcriptarrayYesDetail only. Ordered turns. See Transcripts.
sentiment_timelinearrayYesDetail only. Per-turn sentiment as { t, s, f } items.

Telemetry

All telemetry fields are null when the provider did not report them.

FieldTypeNullableDescription
main_languagestringYesLanguage detected on the call, for example es.
initiation_sourcestringYesHow the conversation was started, as reported by the provider (for example twilio or sip_trunk).
agent_talk_seconds, user_talk_secondsnumberYesSeconds of agent and caller audio.
agent_turn_count, user_turn_countintegerYesNumber of turns per side.
interruption_countintegerYesAgent turns the caller cut off.
avg_response_latency_ms, max_response_latency_msintegerYesAgent response latency, average and worst turn.
cost_creditsnumberYesVoice provider credits charged for the conversation.
cost_usd, llm_cost_usdstringYesCost in USD as a decimal string, for example "0.0412".
llm_input_tokens, llm_output_tokensintegerYesDetail only. Model tokens used.
tool_call_countintegerYesDetail only. Tools the agent invoked. 0 means measured and none used.
mcp_call_countintegerYesDetail only. Tool calls that reached external software (MCP servers and webhooks).
rag_lookup_countintegerYesDetail only. Knowledge base lookups.
tool_result_bytesstringYesDetail only. Characters returned by tool results, as a string.
telemetry_fetched_atstring (date-time)YesDetail only. When telemetry was last fetched.
dnc_scanned_atstring (date-time)YesDetail only. When the transcript was last scanned for an opt-out request.
first_response_msintegerYesDetail only. Reserved; not currently populated.

Transfer

FieldTypeNullableDescription
transferred_tostringYesDetail only. E.164 number of the person the call was transferred to.
transferred_atstring (date-time)YesDetail only. When the warm transfer started.
transfer_call_sidstringYesDetail only. Carrier identifier of the leg dialed to that person. Cleared if the transfer is canceled.

Status lifecycle

StatusTerminalSet when
queuedNoThe call passed every check and its record was created, just before the provider is asked to dial.
ringingNoThe provider accepted the call. On Jelliu’s default outbound path the record stays ringing for the whole conversation, until it ends.
in-progressNoThe carrier reported the call as answered (only for calls whose carrier events reach Jelliu), and for inbound calls, which are created in this status.
completedYesThe provider reported the conversation as done; you hung up a call that had been answered; or the reconciler found it finished.
failedYesThe provider reported a failed conversation or an unknown dial failure; the dial was rejected before connecting; or a record stayed queued for 15 minutes without ever reaching the provider.
no-answerYesThe call rang out.
busyYesThe line was busy.
canceledYesYou hung up before the call was answered, or hung up a call that never reached the carrier; or the carrier reported it canceled.

Terminal statuses never move back to a live status: a late or duplicated event cannot resurrect a closed call. The one upgrade that does happen is to completed: if a call was closed as failed, no-answer, busy or canceled and the provider later delivers a non-empty transcript for it, the status becomes completed because a conversation evidently took place.

Do not wait for in-progress to decide that an outbound call was answered. On the default outbound path it is never set: the call goes from ringing straight to a terminal status. Use the terminal status, duration_seconds and outcome instead.

Status values use hyphens (in-progress, no-answer); outcome values use underscores (no_answer). They are different fields with different vocabularies.

Outcomes

outcome says what the call accomplished, independently of how it ended technically. It is null until analysis is available, which can be seconds or minutes after the call ends.

GroupValues
Salessale_closed, callback_scheduled, rejected
Reachabilityno_answer, voicemail, failed
Handoffescalated_to_human
Supportissue_resolved, ticket_created, faq_answered
Schedulingappointment_booked, appointment_rescheduled, appointment_canceled
Generalinfo_provided, follow_up_needed
Collectionspayment_promised, payment_collected, payment_refused
Retentioncustomer_retained, customer_reactivated, churned
Notificationsmessage_delivered, message_acknowledged
Interviewscandidate_evaluated, candidate_unqualified
Language assessmentlevel_assessed

A call counts as successful when its outcome is a success outcome for its category:

CategorySuccess outcomes
salessale_closed
supportissue_resolved, faq_answered
schedulingappointment_booked, appointment_rescheduled
surveysinfo_provided, issue_resolved, callback_scheduled, faq_answered
collectionspayment_promised, payment_collected
retentioncustomer_retained, customer_reactivated
notificationsmessage_delivered, message_acknowledged
interviewcandidate_evaluated
language_assessmentlevel_assessed
personalinfo_provided
generalinfo_provided, sale_closed, issue_resolved, appointment_booked, faq_answered

Two outcomes are written outside the analysis:

  • no_answer when a call ended without a transcript and never connected (status no-answer, busy, canceled or failed, or zero duration). A call that did connect but whose transcript is late keeps outcome: null until the analysis arrives.
  • escalated_to_human when you complete a warm transfer, unless the call already had an outcome.

Durations and time limits

  • duration_seconds comes from the voice provider when the conversation ends, rounded to whole seconds. If you hang up a call whose carrier leg had already ended, the carrier’s duration is recorded right away; if you hang up a live call, the duration arrives with the post-call report a few seconds later.
  • started_at is the conversation start reported by the provider (for inbound calls, the moment the record is created). ended_at is when Jelliu closed the record.

Every voice call has a hard ceiling after which the platform ends it with a short goodbye. The ceiling comes from the agent’s category, and the agent’s own maximum call duration setting (30 to 3600 seconds, see Agents) can only shorten it:

Agent categoryTarget length the agent paces towardHard ceiling
notifications60 s240 s
interview, language_assessment600 s900 s
Every other category120 s480 s

During a free trial, each call is additionally capped at the trial minutes you have left (never less than 60 seconds).

If a call ends sooner than expected, read termination_reason: it distinguishes a call that hit the maximum duration from one where the agent ended the call itself.

Transcripts

The detail endpoint returns transcript as an array of turns in order:

[
{ "role": "agent", "text": "Hola, ¿hablo con Ana?", "timestamp": "0s", "latency_ms": 820 },
{ "role": "user", "text": "Sí, ella habla.", "timestamp": "3s", "sentiment": 0.1 },
{
"role": "agent",
"text": "Perfecto, le agendo para el jueves a las 10.",
"timestamp": "41s",
"tools": [
{ "name": "book_meeting", "type": "webhook", "ok": true, "action_id": "3f7a2c10-8b4e-4d61-9a0c-5e2f1b7d9c44" }
]
}
]
Turn fieldDescription
roleagent or user.
textWhat was said.
timestampOffset from the start of the call, as a string with an s suffix.
interruptedPresent and true when the turn was cut off.
latency_msAgent turns only: response latency, when measured.
sentimentUser turns only: -1 to 1, when measured.
toolsTools the agent called in that turn: name, type, ok, and action_id for tools that reached external systems.

Transcripts and summaries are kept for your plan’s retention window (7 days on Starter, 30 on Growth, 90 on Business, unlimited on Enterprise), or less if you set a shorter window. Messages from other channels live in Conversations.

Common tasks

Place an outbound call

1

Pick an agent and a destination

You need the id of a provisioned agent (see Agents) and either a phone number in E.164 format or the id of an existing contact.

2

Create the call

curl -sS -X POST "https://api.jelliu.co/api/calls" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"phoneNumber": "+573001234567"
}'

A successful request returns 201 Created with the new record (abridged):

{
"data": {
"id": "5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10",
"tenant_id": "e2a4c6d8-1b3f-4a5c-9e7d-0f2b4d6a8c1e",
"campaign_id": null,
"contact_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"agent_id": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"phone_number": "+573001234567",
"status": "ringing",
"outcome": null,
"duration_seconds": null,
"transcript": null,
"sentiment_score": null,
"summary": null,
"started_at": null,
"ended_at": null,
"call_direction": "outbound",
"prompt_variant_id": null,
"evaluation_criteria_results": {},
"data_collection_results": {},
"created_at": "2026-09-14T15:39:02.118Z",
"updated_at": "2026-09-14T15:39:03.460Z",
"deleted_at": null
}
}
3

Wait for the result

Subscribe to call.completed and call.failed webhooks, or poll GET /api/calls/{callId} until status is terminal.

Request body

agentId
string (uuid)Required

The agent that places the call. It must belong to your workspace, must not be paused, and must have finished provisioning.

phoneNumber
string

Destination in E.164: +, a country code that does not start with 0, and up to 15 digits in total, for example +573001234567. Required unless you send contactId. When both are sent, phoneNumber is the number dialed.

contactId
string (uuid)

An existing contact to call. Its phone number is used when phoneNumber is omitted.

campaignId
string (uuid)

Files the call under a campaign: it counts toward the campaign’s concurrency limit, feeds its reporting, and gives the agent the campaign’s context (product_context, target_audience, campaign name). See Campaigns.

promptVariantId
string (uuid)

A/B testing: the prompt variant to attribute this call to.

promptOverride
string

Replaces the agent’s prompt for this call only. Up to 5000 characters. Text that looks like a prompt injection (for example “ignore previous instructions” or “actúa como”) is rejected.

firstMessageOverride
string

Replaces the agent’s opening line for this call only. Up to 500 characters, same injection screening.

What Jelliu does with the destination

  • Contact filing. The call is always filed under the contact that owns the dialed number. If you omit contactId, Jelliu reuses the oldest existing contact with that number, or creates a contact without a campaign. If you send a contactId whose number differs from phoneNumber, the call is filed under the contact at phoneNumber instead.
  • Campaign consistency. With a campaignId and a phoneNumber that differs from the named contact’s, the number must itself be a contact of that campaign; otherwise the call is refused with 400 VALIDATION_FAILED: El número de destino no pertenece a esta campaña. Agrégalo como contacto de la campaña o haz la llamada sin campaña.
  • Attempt counting. The contact’s call_attempts increases only after the provider accepts the call, so failures before dialing do not use up compliance attempts.
  • Context. The agent receives the contact’s name and what earlier interactions with this person captured, on any channel, so it does not greet a returning customer as a stranger.
  • Disclosures. When the destination requires it, a recording notice is added to the agent’s greeting. A greeting that already announces recording is left as it is.
  • Caller ID. Calls are placed from your workspace’s own number when one is provisioned. During a trial without one, a shared platform number is used. On an active paid plan with no provisioned number the call fails with PHONE_NUMBER_REQUIRED; see Phone numbers.

Compliance checks

Every outbound call, including campaign calls, is checked against the compliance rules for the destination’s country. The country is read from the number’s prefix (+57 Colombia, +52 Mexico, +55 Brazil, +1 United States, +54 Argentina, +56 Chile, +51 Peru; anything else uses the default profile).

CheckDefaultRefusal message
Allowed calling hours08:00 to 20:00 (21:00 for Mexico and the United States)Outside allowed call hours (08:00-20:00 America/Bogota)
Blocked prefixes you configuredNonePhone number matches blocked prefix: +5760
Opt-out suppression listAlways onPhone number is on the opt-out suppression list
Contact marked do-not-callAlways on when a contact is resolvedContact is on the Do Not Call list
Number marked do-not-call on any contactOnPhone number is on the Do Not Call list
Attempts to the contact today3 per dayMax daily call attempts reached (3/day)
Total attempts to the contact10 (8 for Brazil, 15 for the United States)Max total call attempts reached (10 total)

All of them return 403 COMPLIANCE_BLOCKED. You can change the per-country values in Settings → Compliance or through /api/compliance/configs.

The calling-hours window is evaluated in your workspace’s time zone when one is set in your settings, not in the time zone of the country being called. A workspace in Bogotá calling Mexico City is checked against 08:00 to 21:00 Bogotá time. Only when no workspace time zone is set does the country’s own zone apply (Bogotá for numbers outside the modeled countries).

Destination countries. Independently of the person being called, the destination itself must be allowed:

  • Premium-rate and high-risk ranges (for example +1900, +53, satellite +881 to +883, and many Caribbean +1 area codes) are always refused: Calls to premium-rate or high-risk numbers are blocked (toll fraud protection).
  • If you configured allowed calling destinations in Settings → Compliance, only those are reachable.
  • Otherwise the allowed set is Colombia, Mexico, Brazil, United States and Canada (+1), Argentina, Chile, Peru and Spain, plus the country you chose at onboarding and the countries of your own voice numbers. Anything else is refused with This destination is outside the countries this workspace calls. Add it to the allowed calling destinations in Settings → Compliance.

Each refusal is written to your audit log with the masked number.

Abuse protection. A workspace can call the same number at most 5 times per hour (429 CALL_SPAM_DETECTED, Phone number called 6 times in the last hour (max 5)), and place at most 500 calls per hour in total. Trial workspaces calling a number already called by other trial workspaces can be refused with 403 TRIAL_ABUSE_DETECTED.

An opt-out is total. When a person says they do not want to be called again, Jelliu adds their number to the suppression list, marks the contact do-not-call and never dials them again, from any campaign or contact record. See Contacts.

List calls

GET /api/calls uses timestamp-cursor pagination. Filter with agentId and campaignId.

curl -sS "https://api.jelliu.co/api/calls?limit=50&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90" \
-H "Authorization: Bearer $JELLIU_API_KEY"
{
"data": {
"calls": [
{
"id": "5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10",
"agent_id": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"agent_name": "Valentina",
"campaign_id": "0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90",
"campaign_name": "Webinar follow-up",
"contact_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"contact_name": "Ana Gómez",
"phone_number": "+573001234567",
"call_direction": "outbound",
"status": "completed",
"outcome": "callback_scheduled",
"duration_seconds": 184,
"sentiment_score": 0.6,
"summary": "La clienta pidió que la llamen el jueves en la mañana.",
"termination_reason": "end_call tool was called.",
"main_language": "es",
"cost_usd": "0.3912",
"has_recording": true,
"analysis": {
"summary": "La clienta pidió que la llamen el jueves en la mañana.",
"outcome": "callback_scheduled",
"sentiment": "positive",
"sentiment_score": 0.6,
"key_topics": [],
"qualification": {
"score": 85,
"interest": "high",
"answered": true,
"next_step": "Llamar el jueves a las 10:00",
"objections": [],
"reasons": ["Interes high.", "Quedo un siguiente paso concreto: Llamar el jueves a las 10:00."]
}
},
"started_at": "2026-09-14T15:39:05.000Z",
"ended_at": "2026-09-14T15:42:09.771Z",
"created_at": "2026-09-14T15:39:02.118Z",
"updated_at": "2026-09-14T15:42:11.034Z"
}
],
"total": 1284,
"limit": 50,
"offset": 0,
"nextCursor": "2026-09-14T15:39:02.118Z"
}
}

List responses are cached for up to 60 seconds and call details for up to 5 minutes. Status changes Jelliu applies itself (hang-ups, post-call reports, the reconciler) clear the cache, so a freshly ended call normally shows up right away.

Get one call

curl -sS "https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10" \
-H "Authorization: Bearer $JELLIU_API_KEY"

The response is { "data": { ... } } with every field in the call object, including transcript.

Download the recording

GET /api/calls/{callId}/audio streams the recording as binary audio (Content-Type from the provider, audio/mpeg by default). Check has_recording first: it is true once the call is over and has audio to play.

curl -sS -f -o call.mp3 \
"https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10/audio" \
-H "Authorization: Bearer $JELLIU_API_KEY"

A missing recording is always a 404 with code CALL_NOT_FOUND. The X-Recording-Status response header tells the two common cases apart:

X-Recording-StatusMessageWhat to do
processingRecording is still processing — try again in a minute.Retry shortly. Recordings become available a few minutes after the call.
expiredThis recording is no longer available — recordings are kept by the voice provider for a limited time and this call is past that window.Nothing: the audio has been deleted at the source.
(absent)Call has no recording (no conversation_id)The call never started a conversation.

Audio is retained by the voice provider for your plan’s window: 7 days on Starter, 30 on Growth, 90 on Business, and until you delete it on Enterprise. Download recordings you need to keep. Responses carry Cache-Control: private, max-age=300.

Hang up a call

POST /api/calls/{callId}/hangup ends a live call. It is idempotent: calling it on a call that has already ended does nothing and still returns 200.

curl -sS -X POST "https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10/hangup" \
-H "Authorization: Bearer $JELLIU_API_KEY"
{
"data": { "success": true }
}

Jelliu first asks the carrier for the call’s real state, then records the right terminal status:

Carrier state at hang-upRecorded status
Already ended (for example it rang out)The carrier’s own status and duration, for example no-answer.
Answered and talkingcompleted. Duration, transcript and analysis follow with the post-call report.
Not yet answeredcanceled.
The call never reached the carriercanceled.

Hanging up ends the telephone line. The concurrency slot is released immediately; the post-call report still arrives and completes the record.

Transfer a live call to a person

A warm transfer brings a person into a live call. The caller is moved into a conference, Jelliu dials the person you name from your workspace’s number, and both end up on the line. You then either complete the transfer (the person keeps the call) or cancel it (the person’s leg is hung up and the AI agent picks the conversation back up).

These three endpoints are admin operations and require a key with the full scope.

1

Start the transfer

curl -sS -X POST "https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10/transfer" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "transfer_to": "+5716012345" }'
{
"data": {
"transferId": "CA8f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c",
"conferenceId": "CF1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
}
}

transfer_to must be an E.164 number and passes the same destination rules as outbound calls. The call must be live on the carrier: Jelliu checks the carrier directly, not the stored status.

2

Complete or cancel it

# The person keeps the call
curl -sS -X POST "https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10/transfer/complete" \
-H "Authorization: Bearer $JELLIU_API_KEY"
# Or: hang up the person's leg and give the call back to the agent
curl -sS -X POST "https://api.jelliu.co/api/calls/5b0d2f7e-9a41-4c3e-8f0a-2c6d1e7b9a10/transfer/cancel" \
-H "Authorization: Bearer $JELLIU_API_KEY"

These two endpoints answer without a data wrapper:

{ "success": true, "message": "Transfer completed — AI agent disconnected" }
{ "success": true, "message": "Transfer canceled — AI agent resumed" }

What each step does to the call:

  • Complete does not hang up anyone. It sets outcome to escalated_to_human if the call had no outcome yet, and the record closes normally when the caller hangs up.
  • Cancel hangs up the person’s leg, starts a new agent conversation on the caller’s line and clears transferred_to, transferred_at and transfer_call_sid. It fails if the call’s agent is paused or no longer provisioned.

Agents can also hand calls to a person on their own, through a transfer tool configured on the agent. That transfer is executed by the voice provider during the conversation; the destination number is checked against the same destination rules when you save it on the agent. See Agents.

Inbound calls

When someone calls one of your numbers, Jelliu admits the call only if your workspace is not suspended, your plan allows calls and a concurrency slot is free. Then it picks the agent:

  1. The agent assigned to the number. If that agent is paused, the call is not passed to another agent: pausing the assigned agent stops that number from answering.
  2. Routing among your agents, choosing the least busy provisioned agent that is not paused.
  3. Your oldest provisioned, unpaused agent, as a last resort.

Numbers that were removed (soft-deleted) never route calls. An IVR menu, when enabled on the number, is played before routing. Inbound call records are created with call_direction: "inbound", status in-progress and no campaign. Assign agents and configure IVR on the number; see Phone numbers.

Retries

Standalone calls placed with POST /api/calls are never retried. Calls that belong to a campaign and end no-answer, busy, or without connecting are re-dialed according to the campaign’s maxRetryAttempts and retryIntervalMinutes, and each retry passes all the checks on this page again. See Campaigns.

Errors

All errors use the standard error envelope. Codes you can receive from the calls endpoints:

CodeStatusWhen
VALIDATION_FAILED400The body failed validation (Invalid call input, with details.fieldErrors); neither phoneNumber nor contactId was sent; the number is not E.164 (Invalid or missing phone number (E.164 required)); the destination is not a contact of the named campaign; a callId is not a UUID (Invalid call ID); list parameters are out of range (Invalid calls query).
VALIDATION_FAILED400Transfers: Invalid transfer input, Call has no active session — cannot transfer, Call must be in-progress to initiate a transfer, No active transfer for this call, No active transfer to cancel.
VALIDATION_FAILED409Transfers: Could not confirm the call is still live — try again in a moment.
UNAUTHORIZED401Missing or invalid API key. See Authentication.
FORBIDDEN403The key lacks write (placing calls, hanging up) or full (transfers).
BILLING_ERROR403No active plan, trial minutes used up, or the plan’s overage ceiling was reached.
COMPLIANCE_BLOCKED403Outside calling hours, opt-out, do-not-call, attempt limits, blocked prefix, or a destination that is not allowed. The message names the rule.
TRIAL_ABUSE_DETECTED403A trial workspace called a number already used by other trial workspaces.
PHONE_NUMBER_REQUIRED403An active paid plan has no provisioned phone number to call from.
NOT_FOUND404contactId does not exist and no phoneNumber was sent (Contact not found).
AGENT_NOT_FOUND404The agent does not exist in your workspace, or was deleted.
CALL_NOT_FOUND404The call does not exist in your workspace, or its recording is unavailable (see Download the recording).
AGENT_PAUSED409The agent is paused. 503 when its state could not be read; the call is refused as a precaution.
AGENT_PROVISIONING409The agent is still being set up. Retry after Retry-After (5 seconds).
NUMBER_UNVERIFIED400The telephony account cannot dial this destination (trial telephony account or a region not enabled). The record is marked failed.
CALL_SPAM_DETECTED429More than 5 calls to the same number in an hour, or more than 500 calls per hour for the workspace.
MAX_CONCURRENT_CALLS_REACHED429Your plan’s concurrent-call limit, or the campaign’s maxConcurrentCalls, is in use. No Retry-After header; retry when a call ends.
RATE_LIMIT_EXCEEDED429More than 20 requests per minute under /api/calls. Honor Retry-After.
CALL_INITIATION_FAILED502The voice provider rejected the call. The body carries only Internal server error; the record is marked failed and can be inspected with GET /api/calls.
TELEPHONY_ERROR502The carrier failed while starting, completing or canceling a transfer (also when canceling is refused because the agent is paused). The body carries Internal server error.

Do not retry CALL_INITIATION_FAILED or a timeout on POST /api/calls blindly. If the provider accepted the call but a later bookkeeping step failed, Jelliu still returns 201 for the live call; a network timeout on your side may hide a call that is already ringing. List recent calls for the number before dialing again.

Limits

LimitValue
Request rate20 requests per minute per workspace for everything under /api/calls, reads included. Transfers also count against the general per-plan limit. This limit fails closed. See Rate limits.
Concurrent callsStarter 3, Growth 10, Business 25, Enterprise custom (subject to a platform-wide ceiling). Inbound and outbound calls share the same slots. A campaign’s maxConcurrentCalls further caps calls filed under that campaign.
Calls to one number5 per hour per workspace.
Calls per workspace500 per hour.
MinutesTrial: 15 minutes in total, and each call is capped at the minutes left. Paid plans: calls are refused once usage reaches 150% of the plan’s included minutes plus any minute packs bought, until the period renews, a pack is bought or the plan is upgraded.
Call length240 to 900 seconds depending on the agent’s category; see Durations and time limits.
Recording retention7, 30 or 90 days on Starter, Growth and Business; until deleted on Enterprise.
EndpointScope
GET /api/calls, GET /api/calls/{callId}, GET /api/calls/{callId}/audioread
POST /api/calls, POST /api/calls/{callId}/hangupwrite
POST /api/calls/{callId}/transfer, .../transfer/complete, .../transfer/cancelfull

Webhooks

EventSent when
call.completedA call ended and its analysis is available. When the analysis is still being computed, the event waits for it so that outcome is filled in.
call.failedA call ended in a technical failure or could not connect. When the background reconciler closed the call because its end was never reported, the payload is smaller and carries reconciled: true.
crm_sync.completed, crm_sync.failedThe call was written, or failed to be written, to a connected CRM.
agent.action_recordedThe agent invoked a tool during the call.

call.started, call.recording_ready and call.transcript_ready are accepted in subscriptions but are not sent yet. See Webhooks for payloads, signatures and retries.

In call.completed and call.failed, sentimentScore is 0 when sentiment was not measured, while the API returns sentiment_score: null for the same call. Read the call from the API when you need to tell “neutral” from “not measured”.