DevelopersEarly access
Calls
Start a phone call from your own system. One of your agents rings the person, and you read the outcome, the transcript and the recording afterwards.
Before you start
- The agent needs a phone number that can call the person's country. Get one in the agent's Channels section, under Phone.
- From 17 November 2026, a number must be declared for automated calls before it can place one. Record its declaration under Phone numbers › Registration. Numbers Pixl provides are declared by our team.
- Calls use credits from your workspace's wallet. A call your wallet can't cover isn't placed.
- Your key needs
voice:writeto start calls, andvoice:readto read them.
Start a call
POST/voice/call-nowScope: voice:write
Every call is checked before it rings: what it is for, the person's consent, the number, your caller number, your credits and the calling hours. A request that fails a check is refused with a 422 and a sentence that says what to fix. Nothing is created for a refused request.
Body
agent_idstringRequired- The agent that speaks on the call. It must be in this workspace, and not archived or paused.
to_numberstringRequired- The number to ring, with its country code:
+919876543210. A landline, mobile or toll-free number; premium-rate numbers, and numbers of agents on Pixl, are refused. to_countrystring- The number's country, as two letters (
IN), only whento_numberhas no country code. The country sets whose clock the calling hours follow, so Pixl never guesses it. classificationstringRequired- What the call is for.
service: about something they booked or bought.transactional: an update they asked for. It decides which consent rules the call is held to. A call classifiedpromotionalis refused. classification_justificationstring- One sentence, at least 20 characters, on why the call isn't promotional. It's recorded with the call. Leave it out and
opening_contextis used instead (thengoal), which must be as long. consent_attestationbooleanRequired- Must be
true: you confirm this person agreed to be contacted for this purpose. A call without it is refused. It covers this one call. consent_notestring- How they agreed, for example “asked for a callback on our booking form today”. Kept with the consent record. Up to 500 characters.
opening_contextstring- Why you're calling this person, added to the agent's own instructions: their name, the appointment, what to confirm. Up to 2,000 characters.
goalstring- What a good outcome is. Up to 2,000 characters.
from_number_idstring- The id of one of your workspace's numbers, as the caller ID. Leave it out and Pixl picks one: the agent's own number first, and one in the person's country before one elsewhere.
contact_namestring- The person's name, up to 120 characters. A new contact gets it (the first word as the first name). A contact who already has a name keeps it.
variablesobject- Facts from your system the agent can use on the call: up to 20, like
{ "appointment": "Sat 10:30 am", "doctor": "Dr Mehta" }. A name is 1 to 40 characters, a letter first, then letters, digits, spaces or_. A value is up to 200 characters, and all values together up to 2,000. The agent treats them as information, never as instructions. external_refstring- Your own reference for this call, up to 128 characters, like your CRM's id. Pixl returns it on the call, its status and its events.
No offers on a service or update call
service or transactional call can't carry an offer. If opening_context, goal, variables (names as well as values) or the agent's instructions mention a discount or something new to buy, the call is refused. A name written like a field is read as words, so promo_code counts as “promo code”, and look-alike or hidden characters don't get past the check.curl -X POST "https://api.pixlagents.com/api/v1/voice/call-now" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H 'Idempotency-Key: ext_123-confirm-1' \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_123",
"to_number": "+919876543210",
"classification": "service",
"classification_justification": "Confirming the appointment they booked for Saturday.",
"consent_attestation": true,
"consent_note": "Booked a consultation on our website today and asked for a confirmation call.",
"opening_context": "You are calling to confirm the consultation they booked.",
"goal": "They confirm or move the appointment.",
"contact_name": "Asha Rao",
"variables": {
"appointment": "Saturday 10:30 am",
"doctor": "Dr Mehta"
},
"external_ref": "ext_123"
}'{
"campaign_id": "campaign_456",
"recipient_id": "recipient_789",
"contact_id": "contact_321",
"campaign_status": "scheduled",
"to_last4": "3210",
"calling_region": "IN",
"calling_region_source": "stated",
"external_ref": "ext_123",
"status_path": "/api/v1/voice/call-now/campaign_456"
}Keep campaign_id: it's how you follow the call (status_path is its status). The response has no call id yet, because the call doesn't exist until it rings. contact_id is the person in your contacts, found by number or created.
Retrying safely
Send an Idempotency-Key header, up to 128 characters, unique to each call you mean to make. If your request times out and you send it again with the same key, you get the first answer back and nothing rings twice. A refused request doesn't keep its key, so you can fix it and resend. A second request with the key while the first is still being placed gets a 409 with idempotency_key_in_use: try again a moment later. Without a key, two requests ring twice.
- One attempt. If they don't answer, Pixl doesn't ring again; start a new call when you want to.
- If they ask to be called back, the call's
end_callsays when, and a task is left for your team. Pixl doesn't redial on its own.
Calling hours
A call rings only between 10:00 and 21:00 in the person's local time, worked out from the number's country code or to_country. A call started outside those hours waits: its status is window_deferred, and it rings when the hours open there.
This applies to every call started with an API key. A call someone places by hand in the Pixl dashboard is treated differently; a call from your system never is.
Follow a call
GET/voice/call-now/{campaign_id}Scope: voice:read
curl "https://api.pixlagents.com/api/v1/voice/call-now/campaign_456" \
-H "Authorization: Bearer $PIXL_API_KEY"{
"campaign_id": "campaign_456",
"recipient_id": "recipient_789",
"campaign_status": "completed",
"recipient_status": "answered",
"call_id": "call_654",
"error_message": null,
"refusal_code": null,
"refusal_label": null,
"refusal_fix": null,
"external_ref": "ext_123"
}Ask every 10 to 15 seconds while recipient_status is pending or dialing, and stop once it is anything else. call_id appears once the call rings; use it with GET /calls/{call_id}.
Or let Pixl tell you
call.completed and call.refused, and each call's outcome comes to you as it happens, with the same recipient_id and your external_ref. See Webhooks.recipient_status
pending- Waiting to ring. If
campaign_statusispaused, something on your account is holding it, anderror_messagesays what (see below). dialing- Ringing, or on the call.
answered- They picked up. Read what was said with
GET /calls/{call_id}. no_answer- It rang out.
busy- Their line was busy.
voicemail- Voicemail answered.
window_deferred- Outside calling hours where they are. It rings when the hours open.
dnc_skipped- Not called: the number is on your do-not-call list, or no consent covers this call (none on record, expired or withdrawn).
error_messagesays which. failed- Not placed, or the number couldn't be reached. See refusals and fixes below.
Held by your account
When your account stops a call before it rings, campaign_status is paused, recipient_status stays pending, and error_message says why: no credits left, a spending limit reached, the agent paused, no free line, or a caller number that is paused or not declared for automated calls. Fix it, then start the call again.
List calls
GET/callsScope: voice:read
Your workspace's calls, newest first, in pages.
Query
campaign_idstring- Only the calls started by one request: the campaign_id it returned.
agent_idstring- Only this agent's calls.
directionstringinbound,outboundorweb.attempt_outcomestringanswered,no_answer,busy,voicemail,invalid_number,carrier_rejectedorcancelled.started_from, started_todate-time- Only calls that started in this range, in UTC.
qstring- Part of a phone number, in any format.
has_recordingboolean- Only calls with, or without, a recording you can play.
limitinteger- Up to 200 a page. 50 when left out.
cursorstring- The
next_cursorfrom the previous page.
curl -G "https://api.pixlagents.com/api/v1/calls" \
-H "Authorization: Bearer $PIXL_API_KEY" \
--data-urlencode 'direction=outbound' \
--data-urlencode 'limit=50'{
"items": [
{
"id": "call_654",
"agent_id": "agent_123",
"direction": "outbound",
"from_number": "+918012345678",
"to_number": "+919876543210",
"started_at": "2026-10-26T05:02:11",
"answered_at": "2026-10-26T05:02:19",
"ended_at": "2026-10-26T05:04:40",
"duration_seconds": 141,
"attempt_outcome": "answered",
"end_reason": "agent_hangup",
"disposition": "done",
"callback_at": null,
"has_recording": true,
"recording_status": "available",
"cost_credits": 12,
"campaign_id": "campaign_456",
"recipient_id": "recipient_789",
"external_ref": "ext_123",
"contact_id": "contact_321",
"contact_name": "Asha Rao",
"conversation_id": "conv_987"
}
],
"next_cursor": "eyJ0IjoiMjAyNi0xMC0yNiJ9"
}dispositionis how the agent ended the call:done,call_back_later,busy,not_interested,wrong_numberordo_not_call.callback_atis when they asked to be called back, if they did and the agent understood a time.external_refandrecipient_idare set on calls started withPOST /voice/call-now.next_cursorisnullon the last page.
Read one call
GET/calls/{call_id}Scope: voice:read
One call in full: the fields above, plus the transcript and how the agent ended it.
curl "https://api.pixlagents.com/api/v1/calls/call_654" \
-H "Authorization: Bearer $PIXL_API_KEY"{
"id": "call_654",
"attempt_outcome": "answered",
"duration_seconds": 141,
"transcript": [
{
"turn_id": 1,
"role": "assistant",
"text": "Hello Asha, this is Sample Clinic calling about your consultation on Saturday.",
"at": "2026-10-26T05:02:20"
},
{
"turn_id": 2,
"role": "user",
"text": "Yes, 10:30 is fine.",
"at": "2026-10-26T05:02:31"
}
],
"transcript_truncated": false,
"end_call": {
"reason": "done",
"callback_at": null,
"callback_timezone": null,
"callback_text": null,
"note": "Confirmed for Saturday 10:30 am."
},
"recording": {
"status": "available",
"reason": null,
"retention_days": null
},
"content_restricted": false
}In a health business
transcript is empty, the words in end_call are left out, and content_restricted is true. The call's time, length, outcome and cost are always there.Play the recording
GET/calls/{call_id}/recordingScope: voice:read
A short-lived link to the recording. Use url as it comes; if it starts with /, put https://api.pixlagents.com in front. Ask again when it expires.
curl "https://api.pixlagents.com/api/v1/calls/call_654/recording" \
-H "Authorization: Bearer $PIXL_API_KEY"{
"url": "https://recordings.example.com/call_654?signature=abc",
"expires_in": 3600
}When there is no link
409- Still processing. Try again in a minute.
404- No recording for this call.
detailsays why. 410- It was removed under the retention policy.
403- A health business's recording, and your key may not read call content.
Refusals and fixes
A refused start is a 422 with the reason in detail, as a sentence. The common reasons, and what to do:
- No purpose, or an unknown one: send
classificationasserviceortransactional. - A reason under 20 characters: write one sentence on what the call is about.
- No consent: send
consent_attestation: true, once the person has agreed. - No country code on the number: add one, or send
to_country. - Not a number a person answers: an invalid number, a premium-rate one, or an agent's number on Pixl.
- No caller number: none of yours can call that country, or the one you named isn't yours or isn't ready to call yet.
- An offer: the call's details, its variables or the agent's instructions mention one. Take it out.
- A variable that breaks the rules above: too many, a name that isn't allowed, or a value that's too long.
- The agent isn't in this workspace, or is archived or paused.
A 423 means calls are switched off. If your team switched them off, detail says where to switch them back on; otherwise, contact us.
After the start
- A
dnc_skippedor paused call says why inerror_message, in a plain sentence. - A
failedcall that Pixl refused carriesrefusal_code,refusal_labelandrefusal_fix.dial_errormeans the phone network couldn't place it: try again in a minute. - A
failedcall with norefusal_coderang a number that couldn't be reached. Itsattempt_outcomeonGET /calls/{call_id}says why:invalid_number,carrier_rejectedorcancelled. - Treat any code you don't handle as “not called”.
refusal_labeldescribes it.
