Base URL
https://app.cualify.in/api/v1All endpoints are namespaced under /api/v1. We'll version-bump to v2 only on breaking changes; additive changes (new fields, new endpoints) ship without a version bump.
Authentication
Every request needs a Bearer token in the Authorization header. Generate a key in Integrations → API keys; you get a full key starting with cuk_live_. Save it immediately — we never echo the secret again, only the masked prefix.
Authorization: Bearer cuk_live_abc12345_<your-32-char-secret>Keys are scoped to a single org. Revoke any time from the same page — the next request with that key returns 401 unauthorized within ~30 seconds.
Error envelope
Every non-2xx response uses the same JSON shape:
{
"error": {
"code": "invalid_request",
"message": "to_e164 must be E.164 — e.g. +919812345678",
"param": "to_e164",
"details": { /* zod-flat for validation failures */ }
}
}Codes are stable strings (invalid_request, not_found, insufficient_credits, forbidden, upstream_error, internal_error). Switch on code in your client, not on the HTTP status — status can change between minor versions; the code won't.
Endpoints
POST /api/v1/calls — place an outbound call
POST /api/v1/calls
Authorization: Bearer cuk_live_...
Content-Type: application/json
{
"agent_id": "0a8c5a4e-...", // agent uuid from /playbooks
"to_e164": "+919812345678", // recipient
"from_e164": "+918012345678", // optional — defaults to your configured ExoPhone
"language": "hi-IN", // optional — defaults to the agent's language
"max_duration_sec": 300, // optional — defaults to agent's setting
"metadata": { "crm_lead_id": "L-7890" }
}
→ 201 Created
{
"data": {
"id": "e7b1d6c5-...",
"status": "queued",
"agent_id": "0a8c5a4e-...",
"to_e164": "+919812345678",
"provider": "bolna",
"provider_call_id": "boln_...",
"created_at": "2026-05-31T14:22:11Z"
}
}The call status starts as queued and transitions through dialing → in_progress → completed /failed / no_answer / busy. To track: subscribe to call.completed / call.failed webhooks (recommended), or poll GET /api/v1/calls/{id}.
Common pre-call errors: insufficient_credits (top up at /billing), forbidden with reason "No active plan" (renew), not_found on agent_id (verify it's in your org).
GET /api/v1/calls — list calls
GET /api/v1/calls?limit=50&offset=0&status=completed
Authorization: Bearer cuk_live_...
→ 200 OK
{
"data": [
{
"id": "...",
"status": "completed",
"direction": "outbound",
"from_e164": "+918012345678",
"to_e164": "+919812345678",
"language": "hi-IN",
"duration_sec": 142,
"credits_debited_paise": 1420,
"recording_url": "https://...",
"llm_extracted_intent": "demo_booked",
"llm_sentiment": "positive",
"llm_outcome": "demo_scheduled_2026_06_03",
"started_at": "...",
"ended_at": "...",
"created_at": "..."
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1247, "has_more": true }
}Query params: limit (1–100, default 50), offset (default 0), status (one of the 9 status values). Ordered newest-first.
GET /api/v1/calls/{id} — full call detail
Same fields as the list endpoint plus the full transcript (when status=completed) and per-event timeline (disclosure-played, intent- detected, handoff-requested, etc.).
POST /api/v1/contacts — upsert a contact
Use this from your CRM to keep Cualify's contact book in sync. Idempotent on phone_e164 — repeat calls update the existing row.
POST /api/v1/contacts
Authorization: Bearer cuk_live_...
Content-Type: application/json
{
"phone_e164": "+919812345678",
"name": "Riya Sharma",
"email": "[email protected]", // optional
"language_pref": "hi-IN", // optional
"metadata": { "lead_source": "google_ads" },
"consent_capture": "website_form" // pre_call | website_form | manual | imported
}
→ 200 OK
{
"data": { "id": "...", "phone_e164": "+919812345678", "name": "Riya Sharma", "updated_at": "..." }
}GET /api/v1/contacts — list contacts
Paginated. Same limit / offset shape as calls. Filter by consent_capture if you want only the contacts you can legally call.
POST /api/v1/webhooks — register an outbound webhook
Subscribe a HTTPS endpoint to receive event deliveries. Cualify signs every payload with HMAC-SHA256 so you can verify authenticity. Full details: Webhook events + payloads.
POST /api/v1/webhooks
Authorization: Bearer cuk_live_...
Content-Type: application/json
{
"url": "https://your-app.com/cualify-webhook",
"events": ["call.completed", "call.failed"]
}
→ 201 Created
{
"data": { "id": "...", "url": "...", "events": ["call.completed", "call.failed"], "active": true, "created_at": "..." },
"secret": "abcdef0123...64chars" // returned ONCE — store it now
}GET /api/v1/webhooks — list registered endpoints
Returns each registration with its last delivery timestamp + last HTTP status. Useful for monitoring delivery health.
DELETE /api/v1/webhooks/{id} — remove a webhook
Immediate. No 30-day soft-delete — once you DELETE, no further events ship to that URL.
Rate limits
- 120 requests / minute / org across all v1 endpoints (sliding window).
- Rate-limited responses:
429 too_many_requestswith aRetry-Afterheader in seconds. - Higher limits available on Scale / Enterprise — email [email protected].
cURL quickstart
# Place a call
curl -X POST https://app.cualify.in/api/v1/calls \
-H "Authorization: Bearer cuk_live_..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "0a8c5a4e-...",
"to_e164": "+919812345678"
}'
# Poll until completed
curl https://app.cualify.in/api/v1/calls/<id> \
-H "Authorization: Bearer cuk_live_..."SDKs
We don't ship an official SDK yet — the API surface is small enough that customers wire it directly. If you build a Node / Python / Ruby wrapper and want it listed here, send a PR or email [email protected].
What's next?
- Webhook events + payloads — sign verification + retry policy
- Zoho + HubSpot setup — pre-built connectors