Developers
One REST API, for your dashboard and for you.
The dashboard uses the same /v1 API that your servers do. Agents, numbers, contacts, calls, campaigns, triggers, webhooks, calling hours and your statement all work with an API key.
A few things happen only in the dashboard, signed in as a person: recharges and the auto-recharge mandate, API keys, team members and invites, your business details, and test calls to your own verified mobile. The reference below marks them “Dashboard only”.
The OpenAPI 3 document is at https://api.call2all.in/v1/openapi.json.
01
Authentication
Base URL https://api.call2all.in/v1. Send your API key as a bearer token. Keys start with c2a_live_, are stored only as a hash, and carry scopes such as calls:write: a key without the scope an endpoint needs gets 403 scope_missing. Create keys in the dashboard; a key can never create another key.
curl https://api.call2all.in/v1/balance \
-H "Authorization: Bearer $CALL2ALL_KEY"02
Errors
Every error has the same shape and a stable code you can branch on; the message is written for a person. Quote the request_id (also in the x-request-id header) when you write to support.
{
"error": {
"code": "invalid_request",
"message": "Some fields are not valid.",
"details": { "fields": { "to": "Enter a valid Indian phone number, e.g. +91 98765 43210." } },
"request_id": "req_01J…"
}
}- 400
- invalid_request (details.fields names each bad field), idempotency_key_required, token_invalid, csv_invalid
- 401
- unauthenticated, invalid_credentials
- 403
- forbidden, scope_missing, session_required, account_suspended, opt_out_protected
- 404
- not_found, recording_not_found
- 409
- idempotency_conflict, idempotency_in_progress, in_debt, balance_too_low, external_ref_taken, kyc_required, number_in_use, campaign_locked
- 413 · 415
- payload_too_large, file_too_large · unsupported_media_type, unsupported_file_type
- 422
- a rule said no: not_india, on_dnc, marketing_not_allowed, limit_reached, agent_inactive, balance_too_low, recharge_too_small
- 429
- rate_limited (details.retry_after_s), too_many_attempts
- 500 · 502
- internal · telephony_unavailable, payment_gateway_error. Never a stack trace or a partner’s own text
Common codes by status. Each endpoint in the reference lists the statuses it can answer with.
03
Idempotency
POST requests that create something or spend money take an Idempotency-Key header: 1 to 100 visible ASCII characters, kept for 24 hours per account. Each endpoint in the reference says whether it takes one.
- The same key with the same method, URL and body returns the first answer again, with the header
idempotent-replayed: true. - The same key with a different request, or sent by another API key or user, gets
409 idempotency_conflict. - The same key while the first request is still running gets
409 idempotency_in_progress: retry in a moment. - A request that failed is forgotten, so you can retry it with the same key.
- Renting numbers (
POST /v1/numbers) and buying lines (POST /v1/lines) need a key: without one the answer is400 idempotency_key_required.
curl https://api.call2all.in/v1/calls \
-X POST \
-H "Authorization: Bearer $CALL2ALL_KEY" \
-H "Idempotency-Key: order-58213-confirm" \
-H "Content-Type: application/json" \
-d '{ "agent_id": "agt_…", "to": "+919876543210" }'04
Pagination
Lists take limit (1 to 100, default 25) and cursor, and return { "data": [...], "next_cursor": "…" | null }, newest first. Keep passing next_cursor back until it is null. A few short lists, such as API keys, live calls and the catalog, come back whole.
curl "https://api.call2all.in/v1/contacts?limit=100" \
-H "Authorization: Bearer $CALL2ALL_KEY"
# then pass next_cursor back until it is null
curl "https://api.call2all.in/v1/contacts?limit=100&cursor=<next_cursor>" \
-H "Authorization: Bearer $CALL2ALL_KEY"05
Rate limits
Each API key can make 300 requests a minute (a browser session: per IP address). Over that, the answer is 429 rate_limited with details.retry_after_s. Some endpoints have a lower limit of their own, such as testing a webhook endpoint, replaying a delivery or a trigger's hook URL. Request bodies are capped at 1 MB; file uploads have their own limits.
06
Resources
Every resource has a prefixed id and a created_at in ISO-8601 UTC. Where it makes sense, it also takes metadata (up to 20 keys of up to 40 characters; values are text up to 500 characters, numbers, true/false or null) and external_ref (up to 100 characters, for your own id). Money is always an object in paise with a display string. Phone numbers come back in E.164; you may send any Indian format.
{
"id": "con_…",
"created_at": "2026-10-28T08:35:00.000Z",
"external_ref": "client-4471",
"metadata": { "store": "pune-2" },
"phone": "+919876543210"
}
// money, wherever it appears
{ "paise": 45000, "display": "₹450.00" }07
Webhooks
Each delivery is a JSON POST of { id, type, created_at, data }. v1 is the HMAC-SHA256 of <t>.<raw body> with your endpoint's secret; verify it before you parse the JSON. Any answer other than 2xx, a timeout (8 s) or a network error is retried after 10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, then every 6 h, until 24 hours after the event. Every delivery shows in the dashboard and through GET /v1/webhook-deliveries, and can be sent again.
- Call2all-Signature
- t=<unix>,v1=<hex>
- Call2all-Event
- the event type, e.g. call.ended
- Call2all-Event-Id
- evt_…, the same on every retry: de-duplicate on it
- Call2all-Delivery
- whd_…, this delivery
import { createHmac, timingSafeEqual } from "node:crypto";
// header: the Call2all-Signature value, e.g. "t=1761640500,v1=5f2c…"
// rawBody: the request body exactly as received (a string, before JSON.parse)
// secret: the endpoint's whole secret, "whsec_…"
export function verifyCall2all(header, rawBody, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((part) => part.trim().split("=", 2)),
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
// Refuse old deliveries so a captured request can't be replayed later.
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest();
const given = Buffer.from(parts.v1, "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}- Calls
- call.started, call.ended, call.analyzed
- Campaigns
- campaign.started, campaign.completed
- Contacts
- contact.opted_out, import.completed
- Numbers and KYC
- number.held, number.released, kyc.updated
- Money
- balance.low, recharge.succeeded
- Testing
- webhook.test (only from the “send test” button; not subscribable)
08
Price list
GET /v1/prices needs no key. It returns the current per-minute prices, monthly charges and the Silver and Gold tiers. The pricing page on this site reads the same endpoint.
curl https://api.call2all.in/v1/prices09
Endpoint reference
Every endpoint, grouped by area, with the scope it needs. Open one to see its parameters, request body and response fields. It is generated from the API's own schemas, so it changes when the API does.
159 endpoints in 22 groups