REST API reference
Official Cloud API · self-deployed · zero markup: you pay Meta directly.
The Klaros API is a thin, official layer over the WhatsApp Cloud API running on your own deployment. Send messages and templates, read conversations and threads, and list approved templates, all with your data on your infrastructure and no per-message markup. See 12 API use cases with copy-paste recipes for order updates, OTP, helpdesk integration, and more.
Base URL: your deployment, e.g.
https://<your-deployment>/api/v1
Auth: send any API key as a Bearer token:
Authorization: Bearer uni_key_…. Machine-readable spec:
openapi.json.
Two key types: Named developer keys (uni_key_…) are created in the Developers tab with scoped permissions (send, read, manage) and independent revocation. Workspace admin keys (uni_live_…) from Settings have full access. For production integrations, use named keys: give your backend send-only, your BI tool read-only, revoke one without breaking the other.
Quickstart: send your first message
Three steps to your first send
- Create a developer key. In the dashboard, go to Developers → Create key. Choose a name ("production-backend") and scopes (
sendfor messaging,readfor data,managefor mutations). Copy the key. It is shown once. - Set your base URL. Your deployment URL, e.g.
https://your-deployment.example.com. - Send a template. Outside the 24h window, you need an approved template. Inside it, freeform text works too.
# Set your credentials export BASE="https://your-deployment.example.com" export KLAROS_API_KEY="uni_key_…" # Send a template message curl -X POST "$BASE/api/v1/messages" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to":"15551234567","templateName":"hello_world","templateLang":"en_US"}'
The response includes waMessageId and sentStatus. Check delivery via webhooks or the inbox.
Send a message
POST /messages
Send freeform text (only inside the 24-hour customer-service window) or an
approved template (any time). Outside the window a text send returns
409 with needsTemplate: true. Add an optional
from (a phoneNumberId from
/numbers) to send from a specific line; omit it to use your default.
# Text (inside the 24h window) curl -X POST "$BASE/api/v1/messages" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to":"15551234567","text":"Thanks for reaching out!"}' # Approved template, sent from a specific line curl -X POST "$BASE/api/v1/messages" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to":"15551234567","from":"123456789012345","template":{"name":"order_update","language":"en_US"}}'
List conversations
GET /conversations
Most-recent-first, with each conversation's 24h-window state. Query: limit (max 200).
curl "$BASE/api/v1/conversations?limit=50" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Get a conversation thread
GET /conversations/{phone}/messages
Full thread, oldest → newest. phone is E.164 digits (no +).
curl "$BASE/api/v1/conversations/15551234567/messages" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Templates (Meta lifecycle)
Full lifecycle management for your WABA's message templates: create, list, inspect, edit, and delete.
Templates are submitted to Meta for approval. Status progresses through
PENDING → APPROVED or REJECTED.
Editing an approved template resubmits it; the approved version keeps sending until the edit clears review.
GET /templates
List templates with approval status, components, and quality score. Filter by status, category, or name substring. Paginate with after cursor.
curl "$BASE/api/v1/templates?status=APPROVED&limit=50" \ -H "Authorization: Bearer $KLAROS_API_KEY"
GET /templates/:name
Get a template by exact name. Returns full components, quality score, and rejection reason. If the template exists in multiple languages, returns an array.
curl "$BASE/api/v1/templates/order_confirmation" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /templates
Create and submit a template for Meta approval. Name must be lowercase alphanumeric with underscores. Variables in body text ({{1}}) require example values in the component's example field.
curl -X POST "$BASE/api/v1/templates" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "order_shipped", "category": "UTILITY", "language": "en_US", "components": [ { "type": "BODY", "text": "Hi {{1}}, your order {{2}} has shipped. Track: {{3}}", "example": { "body_text": [["Ravi", "ORD-4821", "https://track.example/4821"]] } } ] }'
PATCH /templates/:id
Edit an existing template by its Meta numeric ID (from the id field in list/get responses). Only works on APPROVED or REJECTED templates. PENDING templates cannot be edited. Optionally change the category.
curl -X PATCH "$BASE/api/v1/templates/847291650384" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "type": "BODY", "text": "Hi {{1}}, your order {{2}} shipped via {{3}}. Track: {{4}}", "example": { "body_text": [["Ravi", "ORD-4821", "BlueDart", "https://track.example/4821"]] } } ] }'
DELETE /templates/:name
Delete a template by name. This removes all language variants and is irreversible.
curl -X DELETE "$BASE/api/v1/templates/old_promo" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Numbers (lines)
GET /numbers
Every WhatsApp line connected to your workspace: its phoneNumberId,
friendly label, WABA, and which one is the sending
default. Use a line's phoneNumberId as
from when sending. One business, many numbers, one workspace.
curl "$BASE/api/v1/numbers" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Account & connected number
GET /me
Your account, plan, and the connected WhatsApp number / WABA.
curl "$BASE/api/v1/me" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Webhooks
Register an https endpoint and Klaros POSTs a signed event the moment
something happens on your number. Perfect for n8n, Zapier, or your own backend. Events:
message.received, message.status,
message.optout. Failed deliveries retry automatically with backoff.
POST /webhooks
Register an endpoint. The signing secret is returned once. Store it to
verify the X-Klaros-Signature header. Omit events to receive all.
curl -X POST "$BASE/api/v1/webhooks" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/klaros/webhook","events":["message.received","message.optout"]}' # → 201 { "data": { "id": "…", "secret": "whsec_…", … } } (the secret is shown only here) # List / inspect deliveries / update / delete curl "$BASE/api/v1/webhooks" -H "Authorization: Bearer $KLAROS_API_KEY" curl "$BASE/api/v1/webhooks/$ID/deliveries" -H "Authorization: Bearer $KLAROS_API_KEY" # Update URL, events, or pause/resume (secret stays the same) curl -X PATCH "$BASE/api/v1/webhooks/$ID" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://new.example.com/webhook","active":false}' curl -X DELETE "$BASE/api/v1/webhooks/$ID" -H "Authorization: Bearer $KLAROS_API_KEY"
Event payload & verifying the signature
Each delivery is a JSON body { id, type, created_at, data } with these headers:
X-Klaros-Event, X-Klaros-Delivery, and
X-Klaros-Signature: sha256=<hmac>. The HMAC is SHA-256 of the
raw request body, keyed by your subscription secret. Verify it before trusting the event:
// Node.js: compute and compare import crypto from 'node:crypto'; function verify(rawBody, header, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret) .update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected)); } // Example message.received body { "id": "…", "type": "message.received", "created_at": "2026-06-25T…Z", "data": { "from": "15551234567", "text": "Hi!", "wa_message_id": "wamid…" } }
Connect n8n, Zapier, or Make
There's no published Klaros app in any of these marketplaces yet. What's below uses each platform's own generic webhook/HTTP building blocks against the API and webhooks you already have. It works today with zero waiting on us; a native app would only save you the one-time setup below.
Trigger: Klaros → your workflow (inbound messages, status, opt-outs)
Every platform's "catch a webhook" building block accepts a plain HTTPS URL, so hand it the one your automation gives you, then register it as a Klaros webhook.
- n8n: add a
Webhooknode, copy its Production URL, register it (POST /api/v1/webhooks). VerifyX-Klaros-Signaturein a downstreamCodenode using the snippet below before trusting the payload. - Zapier: create a Zap with trigger Webhooks by Zapier → Catch Hook, copy the custom webhook URL it gives you, register that. Signature verification needs a Code by Zapier step (Node.js) with the same snippet.
- Make: add a Custom webhook trigger module, copy its URL, register that. Verify the signature in an inline HTTP > Set variable + crypto function, or skip verification if the scenario is low-stakes (logging, not money or state changes).
Same signature-check snippet as Webhooks above. Don't skip it for anything that writes data back anywhere.
Action: your workflow → Klaros (send a message, create an order, record a milestone)
Every REST v1 endpoint takes a Bearer $KLAROS_API_KEY. Point your
platform's generic HTTP module at it:
- n8n: an
HTTP Requestnode, or import$BASE/api/v1/openapi.jsondirectly (Import cURL / OpenAPI in newer n8n versions) to get every operation pre-filled with its parameters. Every operation carries a stableoperationId(e.g.postCampaignsByIdLaunch) for tools that key off it. - Zapier: a Webhooks by Zapier → POST/PATCH/GET action, URL
$BASE/api/v1/<resource>, headerAuthorization: Bearer $KLAROS_API_KEY. - Make: an HTTP → Make a request module, same URL/header shape. Make's "Create a custom app" flow can also import the OpenAPI spec above if you want typed fields instead of a raw JSON body.
The most useful single action for most automations is
POST /milestones: one call from a Shopify/any e-commerce
"order created" trigger turns into a WhatsApp confirmation with zero Klaros-side integration work per
store.
Milestones: behavior-triggered WhatsApp
Your customers don't live on a calendar. They place an order, finish onboarding, abandon a cart. The right message is the one that answers that moment, not "day 3 of the drip". Milestones turn your own backend into the trigger: one HTTP call when something meaningful happens, and the rules you define in the dashboard send the contextual next-step template from your own number. No flow-builder ceremony, no per-message markup taxing every trigger: just your event, your template, your price from Meta.
POST /milestones
The whole integration is this call. Fire it from your order handler, your KYC callback, your cron, anywhere your code already knows the customer just did something. Idempotent per contact × milestone: call it on every occurrence, only the first one counts. Unknown phones auto-create a contact.
curl -X POST "$BASE/api/v1/milestones" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"phone":"919876543210","milestone":"order_placed","name":"Priya","meta":{"orderId":"ORD-1042"}}' # → 200 { "data": { "contactId": "…", "milestone": "order_placed", "recorded": true } } # "recorded": false on repeat calls: already achieved, nothing re-fires. Safe to spam.
milestone is your own vocabulary:
2–40 chars, lowercase letters, numbers, underscores (order_placed,
kyc_completed, demo_booked). No
pre-registration needed; a milestone exists the first time you record it.
Built-in: opted_in is
fired by the platform itself the first time a contact opts in, from any source (keyword
reply, button tap, imported consent). Define a rule on it to start a welcome journey with
zero integration; a 30–60 min delay lands it after the instant welcome + cadence
question. A later STOP→START never re-fires it.
GET /milestones?phone=…
A contact's achieved milestones, oldest first, for debugging your integration.
curl "$BASE/api/v1/milestones?phone=919876543210" \ -H "Authorization: Bearer $KLAROS_API_KEY" # → { "data": [ { "milestone": "order_placed", "meta": { "orderId": "ORD-1042" }, # "achievedAt": "2026-07-10T…Z" } ] }
Then the rule does the rest
In the dashboard under Engage → Behavior Sequences, define:
"when order_placed → send template
order_confirmation, after 5 minutes." The engine handles
everything you'd otherwise have to build: one send per contact per rule
(never a double message), consent and opt-out checked at send time (an
opted-out contact is skipped and logged, never messaged), delivery outcomes visible in the
activity feed. The send goes out on your own WABA at Meta's raw conversation price.
Because there's no markup, a high-frequency trigger costs you exactly what it costs Meta.
Need an SDK for your language, or a custom event? Talk to us.
Developer keys
POST /keys
Create a named API key with scoped permissions. The raw key is returned once in the response. Store it securely.
curl -X POST "$BASE/api/v1/keys" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"production-backend","scopes":["send","read"]}'
Scopes:
send = POST /messages, campaign launch.
read = all GETs.
manage = all other mutations.
Default: all three.
Optional:
expiresAt (ISO 8601) sets an automatic expiry for temporary integrations.
GET /keys
List all active keys for the workspace. Raw secrets are never returned after creation.
PATCH /keys/:id
Update a key's name or scopes. Scope changes take effect on the next request.
DELETE /keys/:id
Revoke a key immediately. Any in-flight request using this key will fail. Revocation is permanent.
Best practice: Create one key per integration. Name it after the system it powers ("analytics-dashboard", "crm-sync", "staging-test"). When you rotate, create the new key, update your config, then revoke the old one.
Full resource reference
The endpoints above cover the most common integration patterns. The full API includes 97 endpoints across these resource groups, all documented in the machine-readable OpenAPI 3.1 spec:
curl "$BASE/api/v1/openapi.json" # no auth required
| Resource | Operations | Scope |
|---|---|---|
/contacts | list, get, create, update, delete, bulk upsert, consent | read / manage |
/segments | list, get, create, update, delete, members, preview | read / manage |
/campaigns | list, get, create, update, delete, launch, schedule, pause, resume, analytics, recipients, dry-run | read / manage |
/sequences | list, get, create, update, delete, enroll, unenroll, status, steps CRUD | read / manage |
/products | list, get, create, update, delete | read / manage |
/conversations | list, thread, assign, resolve, reopen, notes, labels | read / manage |
/labels | list, create, update, delete | read / manage |
/canned-replies | list, create, update, delete | read / manage |
/messages | send, get by ID, list with keyset pagination | send / read |
Every mutation endpoint enforces scope. A key with only read + send
can list resources and send messages but cannot create campaigns or modify contacts. See
Developer keys for scope details.
Contacts
Create, query, and manage your contact list. Every contact is identified by phone (E.164 digits, no +). Tags, country, and consent status are filterable. Bulk upsert handles imports up to 1,000 rows at a time.
GET /contacts
List contacts with optional filters: search (name or phone substring), country, tag. Paginate with page and limit (default 50, max 200).
curl "$BASE/api/v1/contacts?search=priya&country=IN&limit=25" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /contacts
Create a single contact. phone is required. Optional: name, country, tags (string array).
curl -X POST "$BASE/api/v1/contacts" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"phone":"919876543210","name":"Priya Sharma","country":"IN","tags":["vip","wholesale"]}'
POST /contacts/bulk
Upsert up to 1,000 contacts in one call. Existing phones are updated, new ones created. countryCode applies as a default for numbers without a country prefix.
curl -X POST "$BASE/api/v1/contacts/bulk" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"countryCode":"91","contacts":[{"phone":"9876543210","name":"Priya"},{"phone":"9123456789","name":"Ravi","tags":["retail"]}]}'
GET /contacts/:id
Get a single contact by ID, including tags, consent status, and last message timestamp.
curl "$BASE/api/v1/contacts/ct_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
PATCH /contacts/:id
Update a contact's name, country, or tags. Omitted fields are left unchanged.
curl -X PATCH "$BASE/api/v1/contacts/ct_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Priya S.","tags":["vip","gold"]}'
DELETE /contacts/:id
Delete a contact and its associated data. This is permanent.
curl -X DELETE "$BASE/api/v1/contacts/ct_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /contacts/:id/consent
Record a consent change. status is opted_in or opted_out. Include source and evidenceText for the audit trail.
curl -X POST "$BASE/api/v1/contacts/ct_abc123/consent" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"opted_in","source":"website_form","evidenceText":"Checked opt-in box on checkout"}'
GET /contacts/:id/consent
Full consent audit trail for a contact: every opt-in, opt-out, source, and timestamp.
curl "$BASE/api/v1/contacts/ct_abc123/consent" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Segments
Dynamic groups of contacts defined by filter rules. Use segments as campaign audiences or sequence enrollment sources. Filters match on tags, country, consent status, and activity.
GET /segments
List segments. Filter by status: active (default) or archived.
curl "$BASE/api/v1/segments?status=active" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /segments
Create a segment. name is required. filters defines the matching rules. Optional description.
curl -X POST "$BASE/api/v1/segments" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Gold customers IN","description":"Opted-in Indian VIPs","filters":{"country":"IN","tags":["vip"],"consentStatus":"opted_in"}}'
GET /segments/:id
Get a segment's definition, filter rules, and current member count.
curl "$BASE/api/v1/segments/seg_xyz789" \ -H "Authorization: Bearer $KLAROS_API_KEY"
PATCH /segments/:id
Update a segment's name, description, or filters.
curl -X PATCH "$BASE/api/v1/segments/seg_xyz789" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filters":{"country":"IN","tags":["vip","gold"]}}'
DELETE /segments/:id
Soft-archive a segment. Archived segments stop matching but remain visible for historical reference.
curl -X DELETE "$BASE/api/v1/segments/seg_xyz789" \ -H "Authorization: Bearer $KLAROS_API_KEY"
GET /segments/:id/contacts
Paginated list of contacts that currently match this segment's filters. page and limit (default 50, max 200).
curl "$BASE/api/v1/segments/seg_xyz789/contacts?page=1&limit=100" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /segments/preview
Stateless preview: pass a filters object and get the matching contact count and sample rows without creating a segment.
curl -X POST "$BASE/api/v1/segments/preview" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filters":{"country":"IN","tags":["wholesale"]}}'
Campaigns
Create, schedule, and manage broadcast campaigns. Campaigns target segments or contact lists and send templates or text messages. Lifecycle: draft, scheduled, active, paused, completed.
GET /campaigns
List campaigns with optional status filter. Paginate with page and limit.
curl "$BASE/api/v1/campaigns?status=active&limit=20" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /campaigns
Create a campaign in draft state. campaignType: one_shot or recurring. messageKind: text or template. audienceSources defines the target segments or contact lists.
curl -X POST "$BASE/api/v1/campaigns" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"July promo","campaignType":"one_shot","messageKind":"template","templateName":"july_sale","audienceSources":[{"type":"segment","id":"seg_xyz789"}]}'
GET /campaigns/:id
Get campaign details, including status, audience size, and delivery progress.
curl "$BASE/api/v1/campaigns/cmp_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
PATCH /campaigns/:id
Update a campaign's name, audience, or message content. Only allowed while the campaign is in draft or paused state.
curl -X PATCH "$BASE/api/v1/campaigns/cmp_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"July promo v2","templateName":"july_sale_v2"}'
DELETE /campaigns/:id
Delete a draft campaign. Active or completed campaigns cannot be deleted.
curl -X DELETE "$BASE/api/v1/campaigns/cmp_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /campaigns/:id/launch
Launch a draft campaign immediately. Consent is checked per-contact at send time.
curl -X POST "$BASE/api/v1/campaigns/cmp_abc123/launch" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /campaigns/:id/schedule
Schedule a campaign for later. Pass scheduledAt (ISO 8601) for one-shot, or recurrence for recurring campaigns.
curl -X POST "$BASE/api/v1/campaigns/cmp_abc123/schedule" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"scheduledAt":"2026-08-01T09:00:00Z"}'
POST /campaigns/:id/pause
Pause an active campaign. Unsent messages are held until resumed.
curl -X POST "$BASE/api/v1/campaigns/cmp_abc123/pause" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /campaigns/:id/resume
Resume a paused campaign. Sending picks up where it left off.
curl -X POST "$BASE/api/v1/campaigns/cmp_abc123/resume" \ -H "Authorization: Bearer $KLAROS_API_KEY"
GET /campaigns/:id/analytics
Delivery analytics: sent, delivered, read, and failed counts with rates.
curl "$BASE/api/v1/campaigns/cmp_abc123/analytics" \ -H "Authorization: Bearer $KLAROS_API_KEY"
GET /campaigns/:id/recipients
Per-contact delivery status for a campaign. Paginate with page and limit.
curl "$BASE/api/v1/campaigns/cmp_abc123/recipients?limit=100" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /campaigns/:id/dry-run
Preview a campaign without sending. Returns the resolved audience, consent checks, and estimated cost.
curl -X POST "$BASE/api/v1/campaigns/cmp_abc123/dry-run" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Sequences
Multi-step drip sequences that send a series of templates over time. Define steps with delays, enroll contacts or segments, and track progress through each step.
GET /sequences
List all sequences with their status and enrollment counts.
curl "$BASE/api/v1/sequences" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /sequences
Create a sequence with an ordered steps array. Each step has kind, templateName, and delayDays (wait before sending).
curl -X POST "$BASE/api/v1/sequences" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Onboarding drip","steps":[{"kind":"template","templateName":"welcome_day1","delayDays":0},{"kind":"template","templateName":"tips_day3","delayDays":3},{"kind":"template","templateName":"checkin_day7","delayDays":7}]}'
GET /sequences/:id
Get a sequence's definition, steps, and enrollment summary.
curl "$BASE/api/v1/sequences/seq_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
PATCH /sequences/:id
Update a sequence's name or status (e.g. active, paused).
curl -X PATCH "$BASE/api/v1/sequences/seq_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"paused"}'
DELETE /sequences/:id
Delete a sequence. Active enrollments are terminated.
curl -X DELETE "$BASE/api/v1/sequences/seq_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /sequences/:id/enroll
Enroll contacts into the sequence. Pass contactIds (max 500) or segmentId to enroll all members of a segment.
curl -X POST "$BASE/api/v1/sequences/seq_abc123/enroll" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contactIds":["ct_001","ct_002","ct_003"]}'
POST /sequences/:id/unenroll
Remove contacts from the sequence. Pending steps are cancelled.
curl -X POST "$BASE/api/v1/sequences/seq_abc123/unenroll" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contactIds":["ct_001"]}'
GET /sequences/:id/status
Enrollment summary: total enrolled, active, completed, and failed counts.
curl "$BASE/api/v1/sequences/seq_abc123/status" \ -H "Authorization: Bearer $KLAROS_API_KEY"
GET /sequences/:id/enrollments
Per-contact enrollment details: current step, next send time, and completion status.
curl "$BASE/api/v1/sequences/seq_abc123/enrollments" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /sequences/:id/steps
Add a step to the sequence. Specify kind, templateName, delayDays, and optional position.
curl -X POST "$BASE/api/v1/sequences/seq_abc123/steps" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"kind":"template","templateName":"feedback_day14","delayDays":14}'
PUT /sequences/:id/steps/:stepId
Replace a step's configuration. The step keeps its position in the sequence.
curl -X PUT "$BASE/api/v1/sequences/seq_abc123/steps/stp_42" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"kind":"template","templateName":"feedback_v2","delayDays":14}'
DELETE /sequences/:id/steps/:stepId
Remove a step from the sequence. Contacts already past this step are unaffected.
curl -X DELETE "$BASE/api/v1/sequences/seq_abc123/steps/stp_42" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Inbox operations
Manage conversations from your API: assign agents, resolve and reopen threads, add internal notes, and organize with labels. Conversations are identified by the contact's phone number (E.164 digits).
POST /conversations/:phone/assign
Assign a conversation to a team member. Pass assignee as an agentId, "me" (the authenticated user), or null to unassign.
curl -X POST "$BASE/api/v1/conversations/919876543210/assign" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"assignee":"agt_ravi42"}'
POST /conversations/:phone/resolve
Mark a conversation as resolved. The contact moves out of the active inbox.
curl -X POST "$BASE/api/v1/conversations/919876543210/resolve" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /conversations/:phone/reopen
Reopen a resolved conversation. It returns to the active inbox.
curl -X POST "$BASE/api/v1/conversations/919876543210/reopen" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /conversations/bulk-resolve
Resolve multiple conversations at once. Pass a phones array (max 200).
curl -X POST "$BASE/api/v1/conversations/bulk-resolve" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"phones":["919876543210","919123456789","918765432100"]}'
POST /conversations/:phone/notes
Add an internal note to a conversation. Visible to your team only, never sent to the contact. body max 4,000 characters.
curl -X POST "$BASE/api/v1/conversations/919876543210/notes" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"body":"Called back, prefers delivery on weekends only."}'
GET /conversations/:phone/labels
List labels attached to a conversation.
curl "$BASE/api/v1/conversations/919876543210/labels" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /conversations/:phone/labels
Attach a label to a conversation by labelId.
curl -X POST "$BASE/api/v1/conversations/919876543210/labels" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"labelId":"lbl_urgent"}'
DELETE /conversations/:phone/labels/:labelId
Remove a label from a conversation.
curl -X DELETE "$BASE/api/v1/conversations/919876543210/labels/lbl_urgent" \ -H "Authorization: Bearer $KLAROS_API_KEY"
Labels CRUD
Manage workspace labels. Each label has a name and color.
# List all labels curl "$BASE/api/v1/labels" \ -H "Authorization: Bearer $KLAROS_API_KEY" # Create a label curl -X POST "$BASE/api/v1/labels" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Urgent","color":"#ef4444"}' # Update / Delete curl -X PUT "$BASE/api/v1/labels/$ID" -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" -d '{"name":"Priority","color":"#f59e0b"}' curl -X DELETE "$BASE/api/v1/labels/$ID" -H "Authorization: Bearer $KLAROS_API_KEY"
Canned replies CRUD
Pre-written reply templates for quick agent responses. Each has a shortcut, title, body, and optional category.
# List canned replies curl "$BASE/api/v1/canned-replies" \ -H "Authorization: Bearer $KLAROS_API_KEY" # Create a canned reply curl -X POST "$BASE/api/v1/canned-replies" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"shortcut":"/hours","title":"Business hours","body":"We are available Mon-Sat 9AM-6PM IST.","category":"general"}' # Update / Delete curl -X PUT "$BASE/api/v1/canned-replies/$ID" -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" -d '{"body":"Mon-Sat 9AM-7PM IST."}' curl -X DELETE "$BASE/api/v1/canned-replies/$ID" -H "Authorization: Bearer $KLAROS_API_KEY"
Products
Manage your product catalog for sharing in WhatsApp conversations. Products are linked to media files uploaded via your dashboard.
GET /products
List products. Pass activeOnly=false to include inactive products.
curl "$BASE/api/v1/products?activeOnly=true" \ -H "Authorization: Bearer $KLAROS_API_KEY"
POST /products
Create a product. name and mediaFileId are required.
curl -X POST "$BASE/api/v1/products" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"22K Gold Chain","mediaFileId":"mf_img_abc123"}'
GET /products/:id
Get a single product's details and media reference.
curl "$BASE/api/v1/products/prod_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
PATCH /products/:id
Update a product's name, media, or active status.
curl -X PATCH "$BASE/api/v1/products/prod_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"22K Gold Chain (20in)"}'
DELETE /products/:id
Delete a product from the catalog.
curl -X DELETE "$BASE/api/v1/products/prod_abc123" \ -H "Authorization: Bearer $KLAROS_API_KEY"
What's new
send / read / manage), independently revocable keys from the Developers tab. Give your backend send-only access; give your BI tool read-only. Revoke one without breaking the other. Docs ↑ Live
POST /api/v1/milestones from your own backend when a customer acts; dashboard rules send the next-step template from your number. React to what customers do, not what day it is. Docs ↑ Live
Reliability
The details that decide whether you can put this in production, not just a demo.
Idempotency
Send an Idempotency-Key header on POST /messages
(any unique string, e.g. a UUID). If the same key arrives again with the same body, the
original response is replayed with "replayed": true and nothing is
sent twice. The same key with a different body returns 409
idempotency_key_reused. Keys are scoped to your workspace and expire after 24 hours.
Retries after a timeout are safe.
curl -X POST "$BASE/api/v1/messages" \ -H "Authorization: Bearer $KLAROS_API_KEY" \ -H "Idempotency-Key: 8f2a1c9e-…" \ -H "Content-Type: application/json" \ -d '{"to":"15551234567","text":"Your order shipped"}'
Rate limits
Every response carries the current window; a 429 tells you exactly
when to retry:
X-RateLimit-Limit: 120 # requests per minute, per workspace X-RateLimit-Remaining: 118 X-RateLimit-Reset: 47 # seconds until the window resets Retry-After: 47 # sent only on a 429
Pace yourself off X-RateLimit-Remaining rather
than waiting for the wall. Need a higher limit? Ask us.
Error codes
Branch on the stable error field, never the human
message. These are a contract:
consent_blocked | 403: recipient opted out, or marketing to a not-yet-opted-in contact outside the 24h window. code gives the reason. |
needsTemplate | 409: the 24h window is closed; send an approved template. |
idempotency_key_reused | 409: same key, different body. |
rate_limited | 429: see Retry-After. |
forbidden_line | 403: your key may not send from that number. |
number_not_found | 404: that from number is not on your workspace. |
provider_error | 502: Meta failed the send; transient, safe to retry. |
The full list is in the OpenAPI spec under x-error-codes.
