Developers
Call Setu REST API
Leads, calls, campaigns, contacts, appointments, SMS, tickets, knowledge bases, workflows, wallet and webhooks — 56 endpoints across 15 modules over HTTPS. Base URL: https://callsetu.in/api/public/v1
Authentication
Bearer token authentication
Create an API key from Dashboard → API reference → API keys & webhooks and pass it in the Authorization header of every request.
curl https://callsetu.in/api/public/v1/me \ -H "Authorization: Bearer kkhs_your_api_key"
Keys are scoped to your workspace (and, for team members, to their permissions). Revoke a key any time; it stops working immediately. Limit: 120 requests per minute per key.
Reference
Endpoints
Conventions
Rules every endpoint follows.
- Authentication
- Send your key as `Authorization: Bearer kkhs_…` over HTTPS. Keys are created in Dashboard → API reference → API keys & webhooks; revoked (or expired) keys stop working immediately. A key made by a team member only reaches the modules that member may use (403 `forbidden_scope` otherwise). The API is server-to-server: there are no CORS headers, so call it from your backend, never from a browser.
- Responses & errors
- Every response is JSON with `ok: true|false`. Errors carry a human `error` message, a machine-readable `code` (e.g. `invalid_body`, `invalid_filter`, `invalid_phone`, `insufficient_balance`, `dnc_blocked`, `rate_limited`) and, for validation failures, `issues: [{ path, message }]` listing every problem with its field path. Dates are ISO 8601. Enum query filters (`status`, `score`, `priority`, `direction`, `outcome`, `type`) accept the listed values in any case; any other value is 400 `invalid_filter`.
- Rate limit
- 120 requests per minute per key, counted before anything else (an over-limit request costs no work). 429 responses carry `Retry-After` (seconds), `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Repeated failed authentications from one address are refused for 10 minutes.
- Pagination
- List endpoints accept `limit` (1–100; default 25, or 100 for agents, phone numbers, groups, knowledge bases, documents and workflows) and return `nextCursor` — pass it back as `?cursor=` for the next page; `null` means you have everything. Ordering is stable (sort key, then id).
- Money & phones
- Amounts are integers in minor units of your account `currency`: paise for INR (`250000` = ₹2,500.00), cents for USD (`2500` = $25.00) — `walletBalancePaisa`, `monthSpendPaisa`, `costPaisa`, `amountPaisa`. Phone numbers are stored in E.164 (`+919876543210`); a bare 10-digit number is treated as Indian.
- Idempotency-Key
- Send an `Idempotency-Key` header (any unique string up to 200 chars, e.g. a UUID) on every POST: /leads, /contacts, /campaigns, /campaigns/:id/contacts, /campaigns/:id/status, /calls/outbound, /calls/:id/hangup, /appointments, /sms, /tickets, /dnc, /contact-groups, /knowledge-bases, /knowledge-bases/:id/documents, /webhooks, /webhooks/:id/test and /workflows/:id/run. Retrying with the same key within 24 h replays the original response (`Idempotent-Replayed: true`) instead of creating a second record, SMS or booking; while the first request is still running a duplicate gets 409 `idempotency_in_progress`. 5xx responses are not stored, so those retries run again.
- Recordings
- `recordingUrl` on calls is a signed, absolute URL that streams the audio without a login; it expires 24 hours after the response was generated — fetch the call again for a fresh link rather than storing it.
Account
Who the key belongs to and the live wallet balance. Money is always in minor units of `currency`: paise for INR tenants, cents for USD tenants.
GET/me
Account details for the API key's owner. `walletBalancePaisa` is in minor units of `currency` (₹2,500.00 → 250000 with INR; $25.00 → 2500 with USD).
curl -X GET https://callsetu.in/api/public/v1/me \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "account": { "id": "…", "name": "…", "email": "…", "company": "…", "timezone": "Asia/Kolkata", "currency": "INR", "walletBalancePaisa": 250000, "createdAt": "…" } }AI agents
Your voice agents. Provider stack and prompts are managed from the panel; the API can rename, enable/disable and set the opening line.
GET/agents
List agents (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/agents \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "agents": [ { "id": "…", "name": "Front desk", "role": "…", "language": "hi-IN", "voice": "female", "active": true, "firstMessage": "…", "createdAt": "…" } ], "nextCursor": "<id or null>" }GET/agents/:id
One agent.
curl -X GET https://callsetu.in/api/public/v1/agents/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "agent": { "id": "…", "name": "…", "role": "…", "firstMessage": "…", "llmProvider": "gemini", "sttProvider": "sarvam", "ttsProvider": "sarvam" } }PATCH/agents/:id
Update name / active / firstMessage.
curl -X PATCH https://callsetu.in/api/public/v1/agents/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "active": false, "firstMessage": "Namaste! Main Riya…" }'Response (example)
{ "ok": true, "agent": { … } }Calls
Place calls, list and filter call logs, read transcripts and recordings, hang up live calls.
POST/calls/outbound
Place an outbound AI call. Uses your active phone number (or `from`). Wallet balance is checked; 402 when insufficient. `metadata` (any JSON object, max 4 KB) is stored on the call and echoed back as `call.metadata` on GET /calls/:id and in call webhooks.
curl -X POST https://callsetu.in/api/public/v1/calls/outbound \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "agentId": "<agent-id>", "from": "+911140000000", "metadata": { "orderId": "1234" } }'Response (example)
{ "ok": true, "call": { "id": "…", "status": "queued", "to": "+919876543210", "from": "…", "provider": "plivo" } }GET/calls
List calls, newest first.
Query: direction=INBOUND|OUTBOUND · outcome=ANSWERED|MISSED|… · agentId · campaignId · phone · live=true|false · from=ISO · to=ISO · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/calls \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "calls": [ { "id": "…", "direction": "INBOUND", "outcome": "ANSWERED", "channel": "AGENT", "callerNumber": "+91…", "fromNumber": "+91…", "durationSecs": 134, "live": false, "sentiment": "POSITIVE", "recordingUrl": "https://…/api/calls/<id>/recording?token=…", "costPaisa": 420, "agentId": "…", "campaignId": null, "metadata": { "orderId": "1234" }, "createdAt": "…" } ], "nextCursor": "<id or null>" }GET/calls/:id
Full call detail with transcript, AI summary, qualification and `metadata`. `recordingUrl` is a signed link valid for 24 h. `latency` is the reply-time summary (milliseconds from the caller going quiet to the agent's first audio: `firstAudioMs` p50/p90/avg/min/max, `turns`, `stack` as stt/llm/tts, `mode`, `speculative` hits/misses, `fillers`) or null for calls without one (before 1.1.76, IVR, unanswered).
curl -X GET https://callsetu.in/api/public/v1/calls/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "call": { "id": "…", "transcript": "…", "aiSummary": "…", "sentiment": "POSITIVE", "qualification": { … }, "recordingUrl": "https://…/api/calls/<id>/recording?token=…", "costPaisa": 420, "metadata": { … }, "variant": null, "attempt": 1, "latency": { "v": 1, "mode": "pipeline", "stack": "deepgram/gemini/azure", "turns": 7, "firstAudioMs": { "p50": 1150, "p90": 1620, "avg": 1210, "min": 820, "max": 1900 }, "sttMs": 210, "llmTtftMs": 620, "ttsTtfbMs": 280, "fillers": 2, "speculative": { "hits": 4, "misses": 1 }, "cacheHits": 1 } } }POST/calls/:id/hangup
End a live call. 409 when it has already ended.
curl -X POST https://callsetu.in/api/public/v1/calls/:id/hangup \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "call": { "id": "…", "live": false } }Leads (CRM)
Lead intake from any form/ads platform, pipeline status changes and timeline notes. Status changes fire `lead.status_changed` webhooks.
POST/leads
Create or update a lead by phone. The phone is normalised to E.164 (`9876543210` → `+919876543210`); when a lead with that number exists, the fields you send (name, email, company, notes) are updated and `created` is false (200, fires `lead.updated`), otherwise a lead is created (201, fires `lead.created`). `call: true` schedules an instant AI callback.
curl -X POST https://callsetu.in/api/public/v1/leads \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "phone": "+919876543210", "name": "Rahul Kumar", "email": "…", "source": "facebook-lead-form", "call": true }'Response (example)
{ "ok": true, "leadId": "…", "created": true, "updated": false, "callScheduled": true }GET/leads
List leads. `phone` is an exact match in any stored form (+91…, 91…, 10 digits); `q` is a substring search.
Query: phone · q (name/phone/email) · status=NEW|CONTACTED|INTERESTED|QUALIFIED|CONVERTED|LOST|DNC · score=HOT|WARM|COLD · source · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/leads \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "leads": [ { "id": "…", "phone": "+919876543210", "name": "…", "status": "NEW", "score": "HOT", "source": "facebook-lead-form", "nextFollowUpAt": null, "createdAt": "…" } ], "nextCursor": "<id or null>" }GET/leads/:id
Lead with its last 50 timeline notes and 20 follow-ups (`notes` itself is the free-text field).
curl -X GET https://callsetu.in/api/public/v1/leads/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "lead": { "id": "…", "phone": "…", "status": "NEW", "notes": "…", "leadNotes": [ { "id": "…", "body": "…", "createdAt": "…" } ], "followUps": [ { "id": "…", "status": "PENDING", "scheduledAt": "…", "reason": "…" } ] } }PATCH/leads/:id
Update fields, move status, add a note, schedule follow-up.
curl -X PATCH https://callsetu.in/api/public/v1/leads/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "QUALIFIED", "nextFollowUpAt": "2026-08-01T10:30:00+05:30", "addNote": "Wants a demo on Monday" }'Response (example)
{ "ok": true, "lead": { … } }DELETE/leads/:id
Delete a lead.
curl -X DELETE https://callsetu.in/api/public/v1/leads/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "deleted": true }Contacts & groups
Contact lists used for campaigns. Contacts live inside a contact group.
GET/contact-groups
List groups with contact counts (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/contact-groups \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "groups": [ { "id": "…", "name": "Diwali offer", "contacts": 1200, "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/contact-groups
Create a group.
curl -X POST https://callsetu.in/api/public/v1/contact-groups \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Diwali offer" }'Response (example)
{ "ok": true, "group": { "id": "…", "name": "…" } }GET/contacts
List contacts.
Query: groupId · q (name/phone) · label · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/contacts \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "contacts": [ { "id": "…", "phone": "…", "name": "…", "labels": ["vip"], "groupId": "…" } ], "nextCursor": "<id or null>" }POST/contacts
Add a contact to a group. The phone is normalised to E.164.
curl -X POST https://callsetu.in/api/public/v1/contacts \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "groupId": "<group-id>", "phone": "+919876543210", "name": "Asha", "labels": ["vip"], "custom": { "city": "Jaipur" } }'Response (example)
{ "ok": true, "contact": { … } }GET/contacts/:id
One contact.
curl -X GET https://callsetu.in/api/public/v1/contacts/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "contact": { … } }GET/pipelines
Your sales pipelines with their ordered stages — the `stageId` values PATCH /contacts/:id accepts.
curl -X GET https://callsetu.in/api/public/v1/pipelines \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "pipelines": [ { "id": "…", "name": "Sales", "stages": [ { "id": "…", "name": "New", "position": 0 } ] } ] }PATCH/contacts/:id
Update name / phone / email / labels / custom / stageId (see GET /pipelines).
curl -X PATCH https://callsetu.in/api/public/v1/contacts/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "labels": ["vip", "repeat"] }'Response (example)
{ "ok": true, "contact": { … } }DELETE/contacts/:id
Delete a contact.
curl -X DELETE https://callsetu.in/api/public/v1/contacts/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "deleted": true }Campaigns
Bulk outbound calling. Create in DRAFT, add contacts, then set status RUNNING to start dialing.
GET/campaigns
List campaigns.
Query: status=DRAFT|SCHEDULED|RUNNING|PAUSED|COMPLETED · type=AGENT|IVR · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/campaigns \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "campaigns": [ { "id": "…", "name": "…", "type": "AGENT", "status": "RUNNING", "language": "hi-IN", "scheduledAt": null, "concurrency": 5, "maxRetries": 1, "dialedCount": 120, "answeredCount": 80, "convertedCount": 12, "dncCount": 3, "agentId": "…", "audienceGroupId": "…", "phoneNumberId": "…", "lastError": null, "lastErrorAt": null, "completedAt": null, "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/campaigns
Create a campaign.
curl -X POST https://callsetu.in/api/public/v1/campaigns \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Q4 outreach", "message": "Namaste! …", "language": "hi-IN", "agentId": "<agent-id>", "audienceGroupId": "<group-id>", "phoneNumberId": "<number-id>", "scheduledAt": "2026-08-01T10:00:00+05:30" }'Response (example)
{ "ok": true, "campaign": { "id": "…", "status": "DRAFT" } }GET/campaigns/:id
Campaign with outcome counts.
curl -X GET https://callsetu.in/api/public/v1/campaigns/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "campaign": { … , "calls": { "ANSWERED": 80, "MISSED": 40 } } }POST/campaigns/:id/contacts
Add numbers to the campaign audience (creates a group if needed). Phones are normalised to E.164; numbers already in the group are skipped, unparseable ones counted as invalid.
curl -X POST https://callsetu.in/api/public/v1/campaigns/:id/contacts \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "contacts": [ { "phone": "+919876543210", "name": "Asha" } ] }'Response (example)
{ "ok": true, "groupId": "…", "added": 1, "skipped": 0, "invalid": 0 }POST/campaigns/:id/status
RUNNING starts dialing (needs contacts + a phone number); PAUSED / COMPLETED stop it.
curl -X POST https://callsetu.in/api/public/v1/campaigns/:id/status \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "RUNNING" }'Response (example)
{ "ok": true, "campaign": { "id": "…", "status": "RUNNING" } }DELETE/campaigns/:id
Delete a campaign (not while RUNNING).
curl -X DELETE https://callsetu.in/api/public/v1/campaigns/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "deleted": true }Appointments
Book against your availability settings (working days, slot length, capacity per slot).
GET/appointments/slots
Free slot start times (ISO instants) for the next N days (max 30), computed in your account timezone.
Query: days=7
curl -X GET https://callsetu.in/api/public/v1/appointments/slots \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "timezone": "Asia/Kolkata", "days": [ { "label": "Mon 3 Aug", "slots": [ "2026-08-03T04:30:00.000Z", "2026-08-03T05:00:00.000Z" ] } ] }GET/appointments
List appointments.
Query: status · phone · from=ISO · to=ISO · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/appointments \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "appointments": [ { "id": "…", "name": "…", "phone": "…", "scheduledAt": "…", "status": "SCHEDULED" } ], "nextCursor": "<id or null>" }POST/appointments
Book a slot start from GET /appointments/slots. 409 `outside_availability` for a past time, a non-working day, a time outside working hours or off the slot grid; 409 `slot_full` when capacity is used up.
curl -X POST https://callsetu.in/api/public/v1/appointments \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Asha", "phone": "+919876543210", "scheduledAt": "2026-08-03T10:30:00+05:30", "notes": "Demo" }'Response (example)
{ "ok": true, "appointment": { … } }GET/appointments/:id
One appointment.
curl -X GET https://callsetu.in/api/public/v1/appointments/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "appointment": { … } }PATCH/appointments/:id
Update status / notes / outcome.
curl -X PATCH https://callsetu.in/api/public/v1/appointments/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "COMPLETED", "outcome": "Signed up" }'Response (example)
{ "ok": true, "appointment": { … } }DELETE/appointments/:id
Cancel an appointment.
curl -X DELETE https://callsetu.in/api/public/v1/appointments/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "cancelled": true }Phone numbers
Numbers assigned to your account and their incoming-call routing.
GET/phone-numbers
List your numbers (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/phone-numbers \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "numbers": [ { "id": "…", "number": "+911140000000", "label": "…", "provider": "plivo", "country": "IN", "active": true, "inboundMode": "AI", "inboundAgentId": "…", "aiPickupDelaySecs": 0, "createdAt": "…" } ], "nextCursor": "<id or null>" }SMS
Send from your active number and read the inbox. DNC numbers are refused.
GET/sms
List messages.
Query: direction=INBOUND|OUTBOUND · phone · from=ISO · to=ISO · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/sms \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "messages": [ { "id": "…", "peer": "+91…", "body": "…", "direction": "INBOUND", "status": "RECEIVED" } ], "nextCursor": "<id or null>" }POST/sms
Send an SMS. Errors: 403 `dnc_blocked`, 409 `no_from_number`, 503 `sms_not_configured` (nothing is stored for these), 502 `provider_error` when the carrier rejects the message.
curl -X POST https://callsetu.in/api/public/v1/sms \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "body": "Your appointment is confirmed for 10:30." }'Response (example)
{ "ok": true, "sent": true, "message": "…" }Support tickets
Tickets raised by your agents or customers.
GET/tickets
List tickets.
Query: status=OPEN|IN_PROGRESS|RESOLVED|CLOSED · priority=LOW|MEDIUM|HIGH · phone · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/tickets \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "tickets": [ { "id": "…", "subject": "…", "status": "OPEN", "priority": "HIGH" } ], "nextCursor": "<id or null>" }POST/tickets
Create a ticket.
curl -X POST https://callsetu.in/api/public/v1/tickets \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "subject": "Refund request", "description": "…", "priority": "HIGH", "phone": "+91…" }'Response (example)
{ "ok": true, "ticket": { … } }GET/tickets/:id
Ticket with replies.
curl -X GET https://callsetu.in/api/public/v1/tickets/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "ticket": { … , "replies": [ … ] } }PATCH/tickets/:id
Update status / priority, add an internal note.
curl -X PATCH https://callsetu.in/api/public/v1/tickets/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "RESOLVED", "addNote": "Refunded ₹500" }'Response (example)
{ "ok": true, "ticket": { … } }DNC & compliance
Your Do-Not-Call list, stored in E.164. POST /sms refuses these numbers (403 `dnc_blocked`) and outbound AI calling honours the list through the shared calling guard.
GET/dnc
List DNC entries.
Query: phone · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/dnc \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "entries": [ { "id": "…", "phone": "+91…", "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/dnc
Add up to 1000 numbers, stored in E.164 (duplicates ignored).
curl -X POST https://callsetu.in/api/public/v1/dnc \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "phones": ["+919876543210", "+919812345678"] }'Response (example)
{ "ok": true, "added": 2, "invalid": 0 }DELETE/dnc
Remove numbers (matches entries stored as +91…, 91… or bare digits).
curl -X DELETE https://callsetu.in/api/public/v1/dnc \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "phones": ["+919876543210"] }'Response (example)
{ "ok": true, "removed": 1 }Knowledge bases
Documents your agents answer from. Add text via API; file uploads are done in the panel.
GET/knowledge-bases
List knowledge bases (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/knowledge-bases \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "knowledgeBases": [ { "id": "…", "name": "Product FAQ", "description": "…", "documents": 12, "agents": 2, "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/knowledge-bases
Create one.
curl -X POST https://callsetu.in/api/public/v1/knowledge-bases \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Product FAQ", "description": "…" }'Response (example)
{ "ok": true, "knowledgeBase": { … } }GET/knowledge-bases/:id/documents
List documents (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/knowledge-bases/:id/documents \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "documents": [ { "id": "…", "type": "TEXT", "name": "…", "url": null, "status": "ready", "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/knowledge-bases/:id/documents
Add a TEXT document (chunked for retrieval on create).
curl -X POST https://callsetu.in/api/public/v1/knowledge-bases/:id/documents \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "type": "TEXT", "name": "Return policy", "content": "Returns accepted within 7 days…" }'Response (example)
{ "ok": true, "document": { … } }Workflows
Automations built in the panel. Start one from your system with any variables.
GET/workflows
List workflows (default 100 per page).
Query: limit · cursor
curl -X GET https://callsetu.in/api/public/v1/workflows \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "workflows": [ { "id": "…", "name": "Missed-call recovery", "trigger": "call.missed", "active": true, "runCount": 42, "lastRunAt": "…", "intervalMinutes": null, "createdAt": "…" } ], "nextCursor": "<id or null>" }POST/workflows/:id/run
Run any active workflow now, whatever its trigger (409 when paused). The body becomes the run variables. If the workflow ends in a Webhook-response node, that node's status and body are returned verbatim; otherwise `status` is DONE or WAITING, and a failed run is `ok: false` with 500 `workflow_failed`.
curl -X POST https://callsetu.in/api/public/v1/workflows/:id/run \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "phone": "+919876543210", "name": "Asha" }'Response (example)
{ "ok": true, "runId": "…", "status": "DONE", "error": null }Wallet & usage
Balance and every credit/debit on the wallet. Amounts are minor units of `currency` (paise for INR, cents for USD).
GET/wallet
Balance and this month's spend in minor units. The month starts at midnight on the 1st in your account timezone (`monthStart`). `balancePaisa` is a deprecated alias of `walletBalancePaisa`.
curl -X GET https://callsetu.in/api/public/v1/wallet \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "wallet": { "walletBalancePaisa": 250000, "monthSpendPaisa": 18400, "monthStart": "2026-08-31T18:30:00.000Z", "currency": "INR" } }GET/wallet/transactions
List transactions.
Query: type=CREDIT|DEBIT · from=ISO · to=ISO · limit · cursor
curl -X GET https://callsetu.in/api/public/v1/wallet/transactions \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "transactions": [ { "id": "…", "type": "DEBIT", "amountPaisa": 420, "description": "Call +91…" } ], "nextCursor": "<id or null>" }Webhooks
We POST JSON `{ id, event, data, sentAt }` to your URL (https, public hosts only). Events: lead.created, lead.updated, lead.status_changed, call.completed, call.missed. Every delivery carries `X-KKHS-Event`, `X-KKHS-Event-Id` (same id on every retry — use it to de-duplicate) and, once the webhook has a secret, `X-KKHS-Signature: t=<unix seconds>,v1=<hex>` where v1 = HMAC-SHA256(secret, `<t>.<raw body>`). Verify with `crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex") === v1` and reject when |now − t| > 300 s. Failed deliveries (non-2xx, timeout) are retried after 1 min, 5 min, 30 min and 2 h (5 attempts in total). The secret is returned once by POST /webhooks; GET only shows `secretPrefix`. Managing webhooks needs a key with the Settings permission (owner keys always have it); a team member's key without Settings gets 403 `forbidden_scope`, even with Automation.
GET/webhooks
List webhooks and the available events.
curl -X GET https://callsetu.in/api/public/v1/webhooks \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "webhooks": [ { "id": "…", "url": "https://…", "events": ["call.completed"], "active": true, "secretPrefix": "whsec_ab12…" } ], "events": [ … ] }POST/webhooks
Add a webhook (https only). `secret` is shown in this response only.
curl -X POST https://callsetu.in/api/public/v1/webhooks \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "url": "https://hooks.zapier.com/…", "events": ["lead.created", "call.completed"] }'Response (example)
{ "ok": true, "webhook": { "id": "…", "secret": "whsec_…", "secretPrefix": "whsec_ab12…", … } }PATCH/webhooks/:id
Change URL / events / active.
curl -X PATCH https://callsetu.in/api/public/v1/webhooks/:id \
-H "Authorization: Bearer kkhs_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "active": false }'Response (example)
{ "ok": true, "webhook": { … } }POST/webhooks/:id/test
Send a signed `webhook.test` payload and report the result (logged as a delivery, not retried).
curl -X POST https://callsetu.in/api/public/v1/webhooks/:id/test \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "delivered": true, "statusCode": 200, "ms": 240 }DELETE/webhooks/:id
Delete a webhook.
curl -X DELETE https://callsetu.in/api/public/v1/webhooks/:id \
-H "Authorization: Bearer kkhs_your_api_key"Response (example)
{ "ok": true, "deleted": true }