Skip to navigation

Create a call

View as Markdown

Places a real outbound phone call in which the agent agentId talks to the destination. Give either a raw phoneNumber (E.164) or a contactId; when both are present phoneNumber wins. The call is dialed synchronously: a 201 means the voice provider accepted it and the phone is ringing (status: ringing). Everything else about the conversation (transcript, outcome, duration) arrives after it ends; follow it with webhooks or GET /api/calls/{callId}.

The call is always filed under a contact. With a contactId whose number matches the destination, that contact is used; otherwise the oldest contact of the workspace with the destination number is used, and a campaign-less contact is created when none exists. If phoneNumber differs from the named contact’s number, the named contact is ignored. With a campaignId, such a different destination must itself be a contact of that campaign, or the call is refused with 400.

Checks before dialing, in order. Each refusal happens before any money is spent:

  1. The agent exists and is not paused (404 / 409 AGENT_PAUSED).
  2. The destination resolves to a valid E.164 number (400).
  3. Compliance (403 COMPLIANCE_BLOCKED): outside the allowed calling hours for the destination’s country, in the workspace time zone (Settings → Compliance; America/Bogota when none is set); a blocked prefix; the number is on the opt-out suppression list; the contact or number is marked Do Not Call; the contact reached its daily or lifetime attempt cap.
  4. Destination policy (403 COMPLIANCE_BLOCKED): premium-rate / high-risk (toll-fraud) prefixes, the workspace blocklist, and the allowed calling countries. Every refusal here writes an audit entry naming the masked number.
  5. Abuse guards: more than 5 calls to the same number in an hour or 500 calls per hour in the workspace (429 CALL_SPAM_DETECTED); a number already called by another trial workspace (403 TRIAL_ABUSE_DETECTED).
  6. Minutes (403 BILLING_ERROR): no active plan, trial minutes (15) used up, or the paid plan’s pay-as-you-go ceiling reached.
  7. The agent finished provisioning (409 AGENT_PROVISIONING with Retry-After: 5), and a paying workspace has its dedicated number (403 PHONE_NUMBER_REQUIRED).
  8. Concurrency (429 MAX_CONCURRENT_CALLS_REACHED): the campaign’s own max_concurrent_calls (message Campaign concurrency limit reached), then the workspace’s plan cap on live calls (Starter 3, Growth 10, Business 25), message Max concurrent calls reached. A slot is freed when a call ends.

Side effects. Dials through the voice engine (over the telephony carrier, or the workspace’s SIP trunk) using the workspace’s own number for the agent as caller ID. For destinations that require it, a recording notice is prepended to the agent’s greeting. The contact’s call_attempts and last_called_at are incremented once the provider accepts the call. Talk time is billed when the call ends, rounded up to whole minutes per call, against included minutes and then overage; a trial call is cut at the remaining trial seconds. Writes an audit entry. If the provider refuses the call, the row is kept with status: failed, the error is returned (400 NUMBER_UNVERIFIED or 502), and call.failed is published for it with reason: error.

Idempotency. Not idempotent and no idempotency key is honoured: retrying after a timeout or a network error can place a second real call to the same person. Check GET /api/calls?agentId=... for a recent row before retrying. Each attempt also counts towards the 5-calls-per-number-per-hour abuse guard.

Webhook events. Exactly one terminal event per call: call.completed ({ call_id, campaign_id, contact_id, agent_id, phone_number, outcome, sentiment_score, duration, summary, data_collection, kpi_data }) when the call had a conversation, sent once its post-call analysis is in, or call.failed ({ call_id, campaign_id, contact_id, agent_id, phone_number, outcome, reason, duration }, reason one of no_answer, busy, failed, canceled, voicemail, error) when it did not, including a call the provider refused and a stale call closed by the reconciler. A campaign call can also trigger campaign.completed. The call also emits call.started, call.answered (when it was answered), call.transferred (when it was transferred) and call.analysis_updated (a later re-analysis). Mutations also emit audit.log_recorded to subscribers. See Webhooks.

Access

  • Required scope: write.
  • Rate limit: Outbound — 20 requests/min per workspace, fail-closed, one budget shared with transfers, email sends and WhatsApp; the request also counts against the General API limit (120 to 600 requests/min by plan). Listing and reading calls do not spend the outbound budget. See Rate limits.
  • Plan: Available on every plan, including trial; requires an active subscription or remaining trial minutes. Concurrent calls are capped per plan.

Authentication

AuthorizationBearer

Workspace API key: jl_ followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (Settings → API Keys) and sent as Authorization: Bearer jl_.... The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys.

ScopeGET / HEADPOST / PUT / PATCH / DELETEAdmin-only routes
readYesNoNo
writeYesYesNo
fullYesYesYes

Operations restricted to admins or owners reject keys without the full scope with 403, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See Authentication.

Request

This endpoint expects an object.
agentIdstringRequiredformat: "uuid"
The agent that talks. Must exist in this workspace, not be paused, and be provisioned with the voice provider.
phoneNumberstringOptionalformat: "^\+[1-9]\d{1,14}$"

Destination in E.164 (Must be E.164 format otherwise). When omitted, the contact’s number is used. When it differs from the contact’s number, the call is filed under the contact that owns this number instead.

campaignIdstringOptionalformat: "uuid"

File the call under this campaign of the workspace (its name, product context and target audience are passed to the agent, and its max_concurrent_calls applies). If phoneNumber overrides the contact’s number, the destination must be one of the campaign’s contacts.

contactIdstringOptionalformat: "uuid"

Contact to call. Its name and earlier conversations on any channel are given to the agent. Ignored when it does not exist and phoneNumber is present.

promptVariantIdstringOptionalformat: "uuid"

A/B test prompt variant this call belongs to; stored on the call as prompt_variant_id for reporting.

promptOverridestringOptional<=5000 characters

Replaces the agent’s prompt for this call only. Rejected when it contains prompt-injection patterns (Prompt contains disallowed injection patterns), e.g. “ignore previous instructions”, “actúa como”, sistema: or zero-width characters. Its use is logged.

firstMessageOverridestringOptional<=500 characters

Replaces the agent’s opening line for this call. Same injection check as promptOverride. A legally required recording notice is still prepended when the destination needs one.

Response

The provider accepted the call and it is ringing. The body is the stored row without the derived analysis, has_recording, agent_name, campaign_name and contact_name.

dataobject

A call as the API serializes it. Field names are snake_case (raw database columns); timestamps are ISO-8601 strings. Vendor identifiers (carrier call SID, voice-provider conversation id) are never returned.

Which fields are present depends on the endpoint:

  • GET /api/calls/{callId} returns every field below.
  • GET /api/calls (list) returns a slimmer row: it omits transcript, first_response_ms, transfer_call_sid, transferred_at, transferred_to, kpi_data, llm_cost_usd, llm_input_tokens, llm_output_tokens, sentiment_timeline, telemetry_fetched_at, tool_call_count, mcp_call_count, rag_lookup_count, tool_result_bytes and dnc_scanned_at.
  • POST /api/calls returns the stored row without the derived fields (analysis, has_recording, agent_name, campaign_name, contact_name).

Telemetry fields are null when not measured, which is not the same as 0. Every field is server-generated; calls cannot be edited through the API.

Errors

400
Bad Request Error
401
Unauthorized Error
403
Forbidden Error
404
Not Found Error
409
Conflict Error
429
Too Many Requests Error
502
Bad Gateway Error
503
Service Unavailable Error