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 }