Skip to content
API reference

The Callario API, as it actually is.

A small, scoped REST API for tickets and contacts, and signed webhooks for what happens in your workspace. This page is written from the code: if an endpoint isn’t listed here, it isn’t public yet.

https://api.callario.com/papi/v13 scopes · 4 endpoints600 requests / 60 s per IP46 webhook events

Overview

Every request goes to https://api.callario.com/papi/v1 over HTTPS, sends and receives JSON, and is authenticated with an API key. The API uses the same services as the Callario console, so the same validation, workspace isolation and audit trail apply to an API request as to a click.

EndpointScopeWhat it does
GET /ticketstickets:readList tickets, filtered and paged
GET /tickets/:idtickets:readOne ticket with its thread
POST /ticketstickets:writeCreate a ticket
GET /contactscontacts:readList customer records

Authentication and scopes

A workspace owner or admin creates keys in Settings → Integrations. A key looks like ck_<prefix>_<secret> and is shown once, when it’s created: Callario stores only a hash of the secret, so a lost key is replaced, not recovered. Revoking a key stops it immediately; to rotate, create the new key, switch over, then revoke the old one. Each key records when it was last used.

Send the key in either header. A key carries one or more scopes, and each endpoint needs exactly one of them.

Authenticating a requestshell
curl https://api.callario.com/papi/v1/tickets \  -H "Authorization: Bearer ck_9f2c41e07a3b5d10_<secret>"# or, equivalentlycurl https://api.callario.com/papi/v1/tickets \  -H "X-Api-Key: ck_9f2c41e07a3b5d10_<secret>"
ScopeGrants
tickets:readGET /tickets and GET /tickets/:id
tickets:writePOST /tickets
contacts:readGET /contacts
Asking for any other scope when creating a key is refused with the scope named — there is no reporting scope, because there is no public reporting endpoint yet.
GET/papi/v1/ticketstickets:read

List tickets

Returns a page of tickets, newest activity first unless you sort otherwise. All parameters are optional query-string values.

ParameterValues
statusopen · pending · on_hold · solved · closed · spam
prioritylow · normal · high · urgent
typequestion · incident · problem · task
viewall · unassigned · open · pending · solved · urgent · breached · at_risk · open_or_pending
qFree-text search, up to 255 characters
assigneeId · tagId · teamId · requesterId · organizationIdUUIDs that narrow the list
sortupdated (default) · newest · oldest · priority · sla_due · waiting
page · pageSizeFrom 1; pageSize 1–100, default 25
Requestshell
curl "https://api.callario.com/papi/v1/tickets?status=open&priority=urgent&sort=sla_due&pageSize=10" \  -H "Authorization: Bearer $CALLARIO_API_KEY"
200 OKjson
{  "data": [    {      "id": "3c0b8a6e-2f4d-4e7a-9a51-6d2f0c1b7e42",      "number": 1042,      "subject": "Can't sign in after changing my email",      "status": "open",      "priority": "urgent",      "type": "question",      "channel": "web",      "requester": { "id": "8d1e…", "name": "Maya Rivera", "email": "maya@northwind.example" },      "assignee": null,      "team": null,      "tags": [],      "messageCount": 3,      "createdAt": "2026-10-04T09:12:44.000Z",      "updatedAt": "2026-10-04T09:20:03.000Z"    }  ],  "total": 1,  "page": 1,  "pageSize": 10}
GET/papi/v1/tickets/:idtickets:read

Get a ticket

Returns one ticket with its messages, events, links, participants, side conversations, custom-field values and SLA clocks. The id must be a UUID — any other value is a 400 — and an unknown id is a 404 with the code TICKET_NOT_FOUND. The ticket’s version increases with each change.

Requestshell
curl https://api.callario.com/papi/v1/tickets/3c0b8a6e-2f4d-4e7a-9a51-6d2f0c1b7e42 \  -H "Authorization: Bearer $CALLARIO_API_KEY"
200 OK (abridged)json
{  "ticket": {    "id": "3c0b8a6e-2f4d-4e7a-9a51-6d2f0c1b7e42",    "number": 1042,    "subject": "Can't sign in after changing my email",    "status": "open",    "description": null,    "version": 4,    "solvedAt": null    // … the list fields above, plus parent, children, mergedInto, closedAt  },  "messages": [ /* the thread, oldest first */ ],  "events": [ /* status changes, assignments, … */ ],  "links": [],  "participants": [],  "sideConversations": [],  "customFields": [],  "sla": null}
POST/papi/v1/ticketstickets:write

Create a ticket

Creates a ticket and answers 201 with it. Tickets created this way are marked origin: "api", so your team can tell them apart from ones a customer or a teammate opened. Name the customer by email, or by the id of an existing customer record.

FieldNotes
subjectRequired, 1–255 characters
descriptionOptional, up to 20,000 characters
prioritylow · normal (default) · high · urgent
typequestion (default) · incident · problem · task
requesterEmail · requesterNameWho the ticket is for, by email address
requesterIdAn existing customer record’s id; wins over requesterEmail when both are sent
assigneeIdA teammate’s id, or null
tagIdsAn array of tag ids
customFieldsValues keyed by custom-field id, validated like the console
Requestshell
curl -X POST https://api.callario.com/papi/v1/tickets \  -H "Authorization: Bearer $CALLARIO_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "subject": "Order arrived damaged",    "description": "Filed from our returns portal.",    "priority": "high",    "type": "incident",    "requesterEmail": "omar@northwind.example",    "requesterName": "Omar Haddad"  }'
201 Created (abridged)json
HTTP/1.1 201 Created{  "id": "b7f3…",  "number": 1043,  "subject": "Order arrived damaged",  "status": "open",  "priority": "high",  "type": "incident",  "origin": "api",  "version": 0  // … the remaining ticket fields}
GET/papi/v1/contactscontacts:read

List contacts

Returns a page of customer records from your CRM. Custom-field values are not included — some may hold personal data — and the API cannot create or edit contacts yet.

ParameterValues
qFree-text search
segment · organizationIdUUIDs that narrow the list
lifecycleStatusThe customer’s lifecycle stage, such as lead or active
vip · hasOrganization · hasEmail · identified · hasOpenTicketstrue or false
tagA tag name
seenWithinDays1–3650
sortrecent (default) · created · name · tickets · last_seen
page · pageSizeFrom 1; pageSize 1–100, default 25
Requestshell
curl "https://api.callario.com/papi/v1/contacts?q=northwind&hasOpenTickets=true&sort=last_seen" \  -H "Authorization: Bearer $CALLARIO_API_KEY"
200 OKjson
{  "data": [    {      "id": "8d1e…",      "displayName": "Maya Rivera",      "primaryEmail": "maya@northwind.example",      "primaryPhone": null,      "avatarUrl": null,      "vip": false,      "lifecycleStatus": "active",      "organization": { "id": "…", "name": "Northwind" },      "tags": ["beta"],      "ticketCount": 4,      "openTicketCount": 1,      "lastSeenAt": "2026-10-04T09:11:58.000Z",      "createdAt": "2026-08-21T14:03:10.000Z",      "updatedAt": "2026-10-04T09:12:44.000Z"    }  ],  "total": 1,  "page": 1,  "pageSize": 25}

Errors

Every error is a application/problem+json document (RFC 9457) with a stable code to branch on, a human-readable title and detail, and a traceId to quote when you ask us about it. Validation errors add an errors array naming each field and what was wrong with it.

StatuscodeWhen
401API_KEY_REQUIREDNo key in either header
401API_KEY_INVALIDThe key is unknown, malformed or revoked
403INSUFFICIENT_SCOPEThe key lacks the endpoint’s scope
404TICKET_NOT_FOUNDNo such ticket in this workspace
422VALIDATION_FAILEDA query or body field is invalid
429RATE_LIMITEDToo many requests — see below
A refused requesthttp
HTTP/1.1 403 ForbiddenContent-Type: application/problem+json{  "type": "about:blank",  "title": "Insufficient API-key scope",  "status": 403,  "detail": "This key lacks the \"tickets:write\" scope.",  "code": "INSUFFICIENT_SCOPE",  "instance": "/papi/v1/tickets",  "traceId": "c41f…"}

Rate limits

The public API allows 600 requests per 60-second window per client IP address, counted across all four endpoints and on top of key authentication. Past that, requests answer 429 with the code RATE_LIMITED and a detail that says how many seconds remain in the window. Back off until then; the window resets on its own.

Webhooks

Add an endpoint in Settings → Integrations and choose its events: exact names, a prefix such as customer.*, or * for everything. Endpoint URLs must use HTTPS and resolve to a public address. Callario generates a signing secret (or takes yours) and shows it once; you can rotate it at any time, and deliveries still waiting to be retried are signed with the new one.

Each delivery is a POST with a JSON envelope — id, type, tenantId, data and occurredAt — and these headers:

HeaderMeaning
x-callario-signaturet = the send time in Unix seconds; v1 = HMAC-SHA256 of “t.body” with your secret. Ignore v0, a legacy body-only signature.
x-callario-timestampThe same t, for convenience — trust the signed one
x-callario-event-idThe event’s id, the same on every retry and replay — your de-duplication key
x-callario-delivery-idThis delivery, as shown in the delivery log
A delivery (abridged)http
POST /webhooks/callario HTTP/1.1Content-Type: application/jsonUser-Agent: Callario-Webhooks/1X-Callario-Signature: t=1759571564,v1=5d1f…a07c,v0=91be…X-Callario-Timestamp: 1759571564X-Callario-Event-Id: 6a0f2d1c-…X-Callario-Delivery-Id: e93b7c55-…{  "id": "6a0f2d1c-…",  "type": "conversation.ticket.status_changed",  "tenantId": "…",  "data": { "ticketId": "3c0b8a6e-…", "from": "open", "to": "solved" },  "occurredAt": "2026-10-04T09:32:44.120Z"}

Verify a signature

Recompute the HMAC over the timestamp, a full stop and the raw request body, compare it with v1 in constant time, and reject anything more than five minutes old. Because the timestamp is inside the signature, a captured delivery can’t be replayed later with a new one.

verify-webhook.mjsnode
import crypto from 'node:crypto';import express from 'express';const app = express();const SECRET = process.env.CALLARIO_WEBHOOK_SECRET;const TOLERANCE_SECONDS = 300;// Verify against the RAW body: re-serialising parsed JSON changes the bytes.app.post('/webhooks/callario', express.raw({ type: 'application/json' }), (req, res) => {  const header = req.get('x-callario-signature') ?? '';  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));  const t = Number(parts.t);  const raw = req.body.toString('utf8');  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${raw}`).digest();  const given = Buffer.from(parts.v1 ?? '', 'hex');  const fresh = Math.abs(Date.now() / 1000 - t) <= TOLERANCE_SECONDS;  if (!fresh || given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {    return res.status(400).send('invalid signature');  }  const event = JSON.parse(raw);  // Deliveries are at-least-once: skip an event.id you have already processed.  res.sendStatus(200);});

Retries and replay

Answer with any 2xx within 8 seconds and the delivery is done. Anything else — another status, a timeout, a refused connection — is retried with exponential backoff: about 1, 2, 4, 8 and 16 minutes after each failed attempt, up to six attempts in all. After the sixth it is marked dead.

AttemptIf it fails, the next one is
1about 1 minute later
2about 2 minutes later
3about 4 minutes later
4about 8 minutes later
5about 16 minutes later
6none — the delivery is dead

The delivery log for each endpoint shows every delivery with its status, HTTP code, duration and the start of your server’s response. From there an admin can replay any delivery — failed, dead or even delivered — and it is sent again, freshly signed. Delivery is at-least-once, so de-duplicate on the event id.

Event catalogue (46)

The names below are checked against what the code emits. Two high-volume events are only sent to endpoints that name them — * and customer.* do not include them.

Tickets12

  • conversation.ticket.createdA ticket was created.
  • conversation.ticket.updatedTicket fields changed.
  • conversation.ticket.status_changedThe ticket moved between statuses.
  • conversation.ticket.assignedThe ticket was assigned or unassigned.
  • conversation.ticket.team_changedThe ticket moved between teams — which changes who can read it.
  • conversation.ticket.taggedThe ticket’s tags changed.
  • conversation.ticket.mergedThe ticket was merged into another.
  • conversation.ticket.splitMessages were split into a new ticket.
  • conversation.ticket.sla_policy_changedA rule bound the ticket to a different SLA policy.
  • ticket.sla.approachingAn SLA target is nearing its deadline.
  • ticket.sla.breachedAn SLA target passed its deadline unmet.
  • ticket.unsnoozedA snoozed ticket returned to the queue.

Conversations9

  • conversation.message.appendedA message was added to a thread.
  • conversation.customer.repliedThe customer replied publicly.
  • conversation.reopenedA resolved conversation was reopened.
  • conversation.chat.startedA new AI chat started.
  • chat.delivery_failedA chat reply did not go out through its WhatsApp, Slack or other channel.
  • channel.email.quarantinedAn inbound email that could not prove which ticket it belongs to is held for review.
  • conversation.chat.intake_completedDeprecated: a chat’s conversation workflow ended — how it ended, the path taken and the visitor’s answers.
  • portal.deflection.recordedA portal visitor was shown, or clicked, a suggested article before filing.
  • csat.respondedA customer answered a satisfaction survey (score, comment, sentiment).

AI2

  • ai.resolution.proposedThe AI agent proposed that a conversation is resolved.
  • ai.resolution.verifiedA proposed resolution was confirmed and held through its reopen window.

Customers17

  • customer.contact.createdA customer record was created.
  • customer.contact.updatedA customer record was updated.
  • customer.contact.mergedTwo customer records were merged.
  • customer.contact.unmergedA merge was reverted.
  • customer.contact.org_associatedA customer was linked to a company.
  • customer.consent.withdrawnA customer withdrew consent (from a teammate, the widget or an unsubscribe link).
  • customer.contact.erasedA customer was erased (GDPR).
  • customer.contact.restrictedProcessing was restricted for a customer (GDPR).
  • customer.dsar.export_completedA data-subject export finished.
  • customer.organization.createdA company was created.
  • customer.organization.updatedA company was updated.
  • customer.organization.deletedA company was deleted.
  • customer.segment.createdA segment was created.
  • customer.segment.updatedA segment was updated.
  • customer.segment.refreshedA dynamic segment was re-evaluated and customers joined or left it.by name only
  • customer.contact.seenA customer came back after more than an hour away (at most once an hour each).by name only
  • customer.bulk_job.completedA bulk customer job (such as an import) finished.

Workflows & rules5

  • workflow.run.failedA workflow run failed — with its step, error and workflow id.
  • workflow.approval.requestedA workflow step is waiting on a person’s decision.
  • workflow.approval.expiredNobody decided in time; the run was cancelled and the gated step never ran.
  • automation.webhook.calledA rule called an outbound webhook.
  • notification.requestedA rule asked for a notification to be sent.

Workspace1

  • identity.user.provisionedA user account was provisioned.

Context endpoint: your data, for the AI agent

The other direction. Register one HTTPS endpoint of your own in Settings → Integrations, and the AI agent can ask it for a record — an order, a shipment, a return or an account — but only for a customer whose identity your website has verified. Requests are signed exactly like webhooks, time out after 5 seconds, and read at most 32 KB of your response. The lookup is read-only: the agent can tell a customer where their order is, not change it.

What your endpoint receiveshttp
POST https://your-company.example/callario/contextContent-Type: application/jsonUser-Agent: Callario-Context/1X-Callario-Signature: t=1759571702,v1=0c9e…{ "type": "order", "ref": "A-10023", "contactRef": "email:maya@northwind.example" }

“Send test” in the console makes three fixed requests, including a badly signed one and a stale one, so an endpoint that skips verification fails the test instead of passing it.

Not in the API yet

So nobody builds against something that isn’t there: the public API does not yet update tickets, post replies, read conversations or chats, create or edit contacts, or return reports. If one of these matters to you, tell us — it shapes what we open next. The product side is on API & Webhooks.

Design-partner programme · now onboarding

Building on Callario? Let’s talk.

Design partners connect their own systems with the founder alongside, and get a direct line for anything this page doesn’t answer.