Kronos API
Send messages, ingest Custom Inbox conversations, and initiate voice calls through the same APIs the Kronos inbox uses, with API-key auth.
Authentication
The API uses Bearer-token auth. Create a key from Settings → API Keys inside the app. The plaintext key is shown ONCE at creation — store it in your password manager or secrets store immediately. Server-side only: never embed it in a browser bundle.
Header format
Authorization: Bearer kronos_sk_live_<random>Scopes
Each key carries one or more scopes. Endpoints reject any key that doesn't hold the required scope with the same generic 401 as no auth at all — we never reveal which scope is missing.
Read account setup, aggregate metrics, agents, and connected inbox metadata.
Send messages on any connected channel.
Look up the status of a message by id.
Place outbound voice calls with one of your AI agents. Opt-in at key creation.
List the voice numbers connected to your account. Opt-in at key creation.
List conversations and their metadata.
Semantic search over the public Kronos docs. Auto-included on every key.
Start safe integration connection flows for the current account.
Create draft agents and update agent configuration.
Assign or reassign agents to inboxes and conversations.
Ingest messages into a Custom Inbox. Can create conversations and trigger agents.
Rotation & revocation
Revoke a key from Settings → API Keys → row dropdown → Revoke. Revocation is immediate and cannot be undone — create a fresh key and update your clients. Expired keys behave identically to revoked ones (the API returns 401).
Errors
Every non-2xx response carries a stable JSON envelope:
{
"error": {
"code": "invalid_payload",
"message": "Request payload failed validation.",
"details": { /* optional, code-dependent */ }
}
}Common error codes
| Status | code | When |
|---|---|---|
| 400 | invalid_body | Body isn't valid JSON. |
| 400 | invalid_payload | Body failed schema validation. details.issues lists fields. |
| 400 | invalid_to | to is not a valid phone number. Use international format starting with +. |
| 400 | context_too_long | The context field on /v1/calls/initiate exceeds 2000 characters. |
| 401 | unauthorized | Missing / invalid / expired / revoked key, or insufficient scope. |
| 402 | quota_exhausted | Your outbound voice budget for this billing period is used up. Retry after your subscription renews. |
| 404 | action_not_found | The requested account action is unknown or not exposed on REST. |
| 404 | not_found | Message id is unknown or doesn't belong to you. |
| 404 | agent_not_found | The supplied agent_id doesn't exist or doesn't belong to your account. |
| 404 | integration_not_found | The supplied integration_id doesn't match the channel or isn't yours. |
| 412 | agent_not_voice_ready | The agent you picked doesn't have the Voice channel enabled. Open the agent editor → enable Voice. |
| 412 | no_integration | You haven't connected the requested channel yet. |
| 412 | multiple_integrations_require_id | You have more than one inbox connected on this channel. Pass integration_id in the body to disambiguate. |
| 413 | payload_too_large | Body exceeds 32 KB. |
| 422 | idempotency_conflict | Same idempotency_key reused with a different request body. |
| 409 | conflict | The account action is valid but cannot be applied in the current state. |
| 429 | rate_limited | Your key exceeded its per-minute budget. Honor the Retry-After header (60 seconds). |
| 502 | send_failed | The downstream channel rejected the message (full error not echoed for privacy). |
| 502 | dial_failed | The outbound carrier refused the call. details.reason carries a short diagnostic code (see Calls section). |
| 503 | action_unavailable | A Tool Kernel account action failed temporarily. |
Rate limits
Each API key is capped at 60 requests / minute. Exceeding the cap returns HTTP 429 with code: rate_limited and a Retry-After: 60 header — back off and retry after the indicated delay. The bucket is per-key and per-endpoint (send and get track separately). Meta's own per-channel limits still apply on top (WhatsApp Business tiers, Messenger rate caps).
If you need higher bursts (e.g. backfilling a campaign), reach out via the in-app support so we can raise the cap on your account.
GET/v1/account/overview
Returns a safe read-only overview of the current account. Requires account:read. The response includes onboarding state, aggregate counts, agents, and connected inbox metadata. It never returns message bodies, prompts, access tokens, API keys, or platform account ids.
Response shape
{
account: {
user_id: string,
display_name: string | null,
company_name: string | null,
onboarding_state: "onboarding" | "dashboard",
onboarding_completed_at: string | null
},
counts: {
agents_total: number,
agents_active: number,
integrations_total: number,
connected_channels: string[],
conversations_total: number,
messages_7d: number,
contacts_total: number,
open_followups: number,
pending_kron_cards: number,
voice_calls_inbound_30d: number,
voice_calls_outbound_30d: number
},
onboarding: { exists: boolean, is_first_onboarding: boolean, use_cases: string[] },
agents: Array<{ id: string, name: string, is_active: boolean, model_id: string | null }>,
integrations: Array<{ id: string, platform: string, account_name: string | null }>
}Example
curl https://api.kronos.com.mx/v1/account/overview \
-H "Authorization: Bearer $KRONOS_API_KEY"POST/v1/account/actions/{action}
Execute an account-scoped Tool Kernel action with the same handler used by Kron and MCP. The action always runs as the API key owner; callers cannot pass user_id or target another account.
Available actions
| action | scope | Purpose |
|---|---|---|
| start_integration_connection | integrations:connect | Return a safe first-party URL to start an integration login flow. |
| create_draft_agent | agents:write | Create an inactive draft AI agent. |
| update_agent_configuration | agents:write | Update safe editable fields on an account-owned agent. |
| reassign_agent | agents:assign | Assign an agent to an inbox or specific conversations. |
Body schemas
// start_integration_connection
{ provider: "instagram" | "facebook" | "messenger" | "whatsapp" | "gmail" | "shopify" | "telegram" | "mcp", return_to?: string }
// create_draft_agent
{ name: string, description?: string, prompt?: string }
// update_agent_configuration
{ agent_id: string, name?: string, description?: string, prompt?: string, is_active?: boolean, memory_level?: "disabled" | "simple" | "advanced" | "pro", followup_mode?: "disabled" | "classic" | "agentic" | "guided", temperature?: number }
// reassign_agent
{ target_type: "inbox", inbox_id: string, agent_id: string }
// or
{ target_type: "conversations", conversation_ids: string[], agent_id: string }Response shape
{
"action_name": "create_draft_agent",
"data": { "...": "action-specific payload" }
}POST/v1/custom-inboxes/{integration_id}/messages
Ingest one inbound message into a Custom Inbox. Requires custom_inbox:write scope. The API stores the message and returns immediately; agent replies and activity events are sent later to the webhook configured on the inbox.
Body schema
{
external_user_id: string,
external_conversation_id?: string,
message_id: string, // required idempotency key
content: string, // 1..4096 chars
sender?: { name?: string, email?: string, phone?: string },
metadata?: Record<string, unknown>,// max 10KB and 50 top-level keys
new_conversation?: boolean
}Accepted response
{
"status": "accepted",
"integration_id": "4ad91e83-7d48-4371-9f2f-4505c7912c2e",
"conversation_id": "b8e547b4-3837-49d2-9f4b-6c2e9d01fd7d",
"external_conversation_id": "thread_abc123",
"inbound_message_id": "99618f3e-4f7a-4e17-896e-250f6a92db31",
"idempotent_replay": false,
"created_at": "2026-07-06T09:12:27.852Z"
}Example
curl -X POST https://api.kronos.com.mx/v1/custom-inboxes/4ad91e83-7d48-4371-9f2f-4505c7912c2e/messages \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "user_123",
"external_conversation_id": "thread_abc123",
"message_id": "msg_0001",
"content": "Hola, quiero revisar mi pedido",
"sender": { "name": "María" },
"metadata": { "source": "mobile_app" }
}'Webhook events
Custom Inbox sends message.created, agent.reasoning.created, agent.tool.called, and webhook.test. Every delivery includes X-Kronos-Event-Id, X-Kronos-Timestamp, and X-Kronos-Signature where the signature is v1=<hmac_sha256(timestamp.raw_body)>.
POST/v1/messages/send
Dispatch a text, template, or media message on one of your connected channels. Requires messages:send scope.
Body schema
{
channel: "whatsapp" | "instagram" | "messenger" | "telegram",
integration_id?: string, // optional when you have ONE inbox on this channel;
// REQUIRED when you have multiple (else 412)
recipient: string, // wa_id | IG PSID | Messenger PSID
message: {
type: "text";
text: string // max 4096 chars
} | {
type: "template"; // WhatsApp only
template: {
name: string,
language: string, // e.g. "en_US"
components?: Array<{ type, parameters?: object[] }>
}
} | {
type: "media";
media: {
url: string, // public URL
mediaType: "image" | "video" | "audio" | "document",
caption?: string // max 1024 chars
}
},
tag?: "CONFIRMED_EVENT_UPDATE" | "POST_PURCHASE_UPDATE"
| "ACCOUNT_UPDATE" | "HUMAN_AGENT", // IG / Messenger >24h
idempotency_key?: string // 8..128 chars
}Success response
{
"id": "wamid.HBgN...", // Meta's platform_message_id
"channel": "whatsapp",
"status": "sent",
"created_at": "2026-05-11T07:42:27.852Z"
}Example — WhatsApp text (within 24h session)
curl -X POST https://api.kronos.com.mx/v1/messages/send \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"recipient": "5215555555555",
"message": { "type": "text", "text": "Hi! Your order is ready." }
}'Example — WhatsApp template (outside 24h window)
Outside the 24h window WhatsApp requires a pre-approved template. Manage templates in Settings → WhatsApp.
curl -X POST https://api.kronos.com.mx/v1/messages/send \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"recipient": "5215555555555",
"message": {
"type": "template",
"template": {
"name": "appointment_reminder",
"language": "en_US",
"components": [{
"type": "body",
"parameters": [
{ "type": "text", "text": "Maria" },
{ "type": "text", "text": "Tuesday at 10:00 AM" }
]
}]
}
}
}'Example — Instagram with MESSAGE_TAG (outside 24h)
Messenger and Instagram require a tag for outbound messages outside the 24h customer-session window.
curl -X POST https://api.kronos.com.mx/v1/messages/send \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "instagram",
"recipient": "psid_1234567890",
"tag": "POST_PURCHASE_UPDATE",
"message": { "type": "text", "text": "Tracking #ABC123 has shipped." }
}'Example — Messenger media (image with caption)
curl -X POST https://api.kronos.com.mx/v1/messages/send \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "messenger",
"recipient": "psid_1234567890",
"message": {
"type": "media",
"media": {
"url": "https://cdn.example.com/receipt.png",
"mediaType": "image",
"caption": "Here is your receipt."
}
}
}'Example — TypeScript with fetch
const res = await fetch("https://api.kronos.com.mx/v1/messages/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KRONOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
channel: "whatsapp",
recipient: "5215555555555",
message: { type: "text", text: "Hi from TS!" },
idempotency_key: `order-confirmation-${orderId}`,
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Kronos API ${res.status}: ${err.error?.code}`);
}
const { id, channel, status, created_at } = await res.json();Idempotency
Pass idempotency_key (8..128 chars) to safely retry a send. The first successful response is stored for 30 days under (your_user, idempotency_key). Repeating the call with the EXACT same body returns the original response plus a header:
X-Idempotent-Replay: trueIf you reuse the key with a DIFFERENT body you get 422 idempotency_conflict — this protects you from a buggy retry loop silently swallowing a different send.
GET/v1/messages/{id}
Look up an outbound message by its platform_message_id (the same id returned from POST /v1/messages/send and from Meta's delivery webhooks). Requires messages:read scope.
Lookup scope
This endpoint currently returns messages that originated from the Kronos inbox UI or inbound webhook deliveries. Messages dispatched through POST /v1/messages/send are NOT (yet) stored in the queryable message log — the id returned by /send is Meta's platform message id, intended for correlation with delivery webhooks. To confirm delivery or read receipts of an API-sent message, subscribe to your channel's webhooks.
Success response
{
"id": "wamid.HBgN...",
"channel": "whatsapp",
"content": "Hi! Your order is ready.",
"message_type": "text",
"created_at": "2026-05-11T07:42:27.852Z"
}Example
curl https://api.kronos.com.mx/v1/messages/wamid.HBgN1234567890ABCDEF \
-H "Authorization: Bearer $KRONOS_API_KEY"Channel matrix
| Capability | Messenger | Telegram | Custom Inbox | ||
|---|---|---|---|---|---|
message.type: text | Yes | Yes | N/A webhook | Yes | Yes |
message.type: template | Yes | No | No | N/A | No |
message.type: media (image / video / audio / document) | Yes | Yes | Yes | No (roadmap) | No (text MVP) |
tag (outside 24h window) | N/A (use template) | Required | Required | N/A (no window) | N/A |
| 24h-window text without tag/template | Within window only | Within window only | Within window only | Any time AFTER first user message | N/A |
recipient shape | string (wa_id) | string (PSID) | string (PSID) | { chat_id } | external_conversation_id |
The 24h window is Meta's policy: you may freely send free-form messages in the 24 hours following the customer's last inbound. Outside that, WhatsApp requires a pre-approved template and Messenger / Instagram require a MESSAGE_TAG.
POST/v1/calls/initiate
Place an outbound voice call. The recipient picks up and hears your AI agent — your code initiates the dial; the agent has the conversation. Requires calls:initiate scope.
Body schema
{
to: string, // phone number to call, international format
// (with or without leading "+"; both work).
// 8-15 digits, first non-zero.
agent_id: string, // UUID of the AI agent that should voice
// the call. The agent MUST have the Voice
// channel enabled.
context?: string, // optional briefing for the agent ("background"
// not "instructions"). Max 2000 chars.
integration_id?: string, // optional voice number id. Required only if
// you have more than one voice number connected.
tag?: string, // optional free-form correlation id (lead id,
// ticket number, etc.). Max 100 chars.
idempotency_key?: string // 8..128 chars. Reuse to safely retry; same
// key + same body returns the original
// response with X-Idempotent-Replay: true.
}Success response
{
"call_id": "7d63af24-68dc-4928-b8f7-cab7c317964a",
"twilio_call_sid": "CA159e953f1b7aa13831b4c4445cb412fa",
"from": "+15187329372",
"to": "+5215512345678",
"status": "initiated",
"agent_id": "547ce9ba-55b5-4992-baaa-bdfb232310aa"
}call_id is the Kronos conversation id — open /app/chat?inbox=<integration_id>&c=<call_id> in the dashboard to watch the live transcript.
Example — curl
curl -X POST https://api.kronos.com.mx/v1/calls/initiate \
-H "Authorization: Bearer $KRONOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+5215555555555",
"agent_id": "547ce9ba-55b5-4992-baaa-bdfb232310aa",
"context": "Customer asked about onboarding. They are in CDMX. Speak Spanish.",
"idempotency_key": "lead-followup-2026-05-22-001"
}'Diagnostic codes
When the carrier refuses the call before the line connects, the response is 502 dial_failed and details.reason carries a short code. The most common ones:
| Reason | Meaning |
|---|---|
| 21215 | Your account is not enabled to place calls to that country / region. Contact support to expand geographic permissions. |
| 21211 | The destination number is not valid (despite passing our format check). |
| 13225 / 21219 | The destination is not reachable right now. Try again later. |
| failed | The carrier accepted the dial request but then refused to connect the call (malformed destination, blocked carrier, etc.). Most common cause: a number that's well-formed against the global E.164 regex but invalid for its specific country numbering plan. |
| busy | The destination is busy. Try again in a moment. |
| no-answer | The destination did not answer in time. |
| canceled | The call was canceled before it could connect. |
| carrier_unreachable | Kronos couldn't reach the upstream carrier. Transient — retry. |
| conversation_upsert_failed | Internal database issue creating the conversation row. Rare — file a ticket. |
If you see a reason code not in this list, contact in-app support — we will surface the meaning and update this table.
Idempotency
Pass idempotency_key (8..128 chars) to safely retry. The first successful response is stored for 30 days under (your_user, idempotency_key). Repeating the call with the EXACT same body returns the original response plus header X-Idempotent-Replay: true. Reusing the key with a DIFFERENT body returns 422 idempotency_conflict. Failed calls (dial_failed) are NOT stored — retrying with the same key after a failure DOES re-attempt the dial.
Quotas
Each subscription includes a 30-minute outbound budget per billing period. When exhausted, this endpoint returns 402 quota_exhausted upfront — no dial is placed, no charges incur. The budget resets when your subscription renews.
GET/v1/voice/numbers
List the voice numbers connected to your account. Useful for picking the right integration_id for /v1/calls/initiate when you have multiple voice numbers. Requires calls:read scope.
Success response
{
"numbers": [
{
"integration_id": "c7fa52fa-83f1-446e-bbc3-a8a628ec24e7",
"phone_number": "+15187329372",
"inbox_name": "Support",
"iso_country": "US",
"default_agent_id": "547ce9ba-55b5-4992-baaa-bdfb232310aa"
}
]
}numbers is always an array even when only one voice number is connected. default_agent_id is the agent the inbox uses for inbound calls — you may pick a different agent per outbound call via the agent_id body field on /v1/calls/initiate.
Example — curl
curl https://api.kronos.com.mx/v1/voice/numbers \
-H "Authorization: Bearer $KRONOS_API_KEY"