Public API

Tikivio’s public API connects your own tools to your help desk: read, create and update tickets, reply to them, look up your requesters and agents, and receive signed notifications when a ticket changes.

1. Create a key

An organisation administrator creates the key in Settings › API (/o/<your-organisation>/parametres/api) and chooses its permissions:

PermissionWhat it allows
tickets:readsee tickets, their messages and the list of agents
tickets:writecreate tickets, change their status, priority or assignee, and write messages
contacts:readsee requesters (name and e-mail address)
contacts:writeadd requesters

The key is shown only once: keep it in your server’s secrets, never in a web page or in an app you distribute. It acts on behalf of the administrator who created it, with at most the permissions that person still has. It can be revoked at any time from the same screen, which also shows the latest calls received.

2. First call

curl -s "https://app.tikivio.com/api/v1/tickets?limit=10" \
  -H "Authorization: Bearer $TIKIVIO_API_KEY"

Every address starts with https://app.tikivio.com/api/v1. Requests and responses are JSON (UTF-8); dates are in UTC, formatted like 2026-11-02T08:30:00Z. The full description of the API, in OpenAPI 3.1 format, is public, at https://app.tikivio.com/api/v1/openapi.json. You can import it into Postman, Insomnia or a code generator.

3. What the API does

CallPurposePermission
GET /ticketslist tickets, most recent first; filters status (comma-separated values) and updated_sincetickets:read
POST /ticketscreate a tickettickets:write
GET /tickets/{reference}one ticket, by its reference (for example TIKI-000123)tickets:read
PATCH /tickets/{reference}change the status, priority or assigneetickets:write
GET /tickets/{reference}/messagesthe messages of a tickettickets:read
POST /tickets/{reference}/messagesadd a public reply or an internal notetickets:write
GET /contactsrequesters; filter emailcontacts:read
POST /contactsadd a requester, or find the one who already has this addresscontacts:write
GET /contacts/{id}one requestercontacts:read
GET /agentsactive agents, to assign a tickettickets:read
GET /organisationthe name of the key’s organisation and email_replies (see “Create a ticket”)any key

Lists return data and next_cursor: pass cursor=<next_cursor> to get the next page; next_cursor is null on the last page. limit ranges from 1 to 200 (50 by default).

4. Create a ticket

curl -s -X POST "https://app.tikivio.com/api/v1/tickets" \
  -H "Authorization: Bearer $TIKIVIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4521-return" \
  -d '{"requester_email":"sam@example.com","requester_name":"Sam Taylor",
       "subject":"Return of order 4521","body":"Hello, the parcel arrived damaged.",
       "priority":"normal"}'

Required fields: requester_email, subject and body (plain text, 1 MiB at most); priority is low, normal, high or urgent. The 201 response returns the new ticket with its reference, and email_replies: "available" when the organisation has a mailbox with sending enabled, "unavailable" otherwise. With "unavailable", do not tell the requester they will get the reply by e-mail. To know it before creating a ticket, call GET /organisation, which returns only name and email_replies.

With the Idempotency-Key header (1 to 255 ASCII characters), the same request sent again within 24 hours returns the same response without creating a duplicate (header Idempotent-Replayed: true); the same key with different content is refused (422).

5. Reply and update

curl -s -X POST "https://app.tikivio.com/api/v1/tickets/TIKI-000123/messages" \
  -H "Authorization: Bearer $TIKIVIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"We are sending you a new parcel today.","internal":false}'

curl -s -X PATCH "https://app.tikivio.com/api/v1/tickets/TIKI-000123" \
  -H "Authorization: Bearer $TIKIVIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"pending_requester","reason":"New parcel sent"}'

With "internal": true, the message becomes an internal note, never visible to the requester. Statuses: new, open, pending_requester, pending_internal, resolved, closed. To assign a ticket, send assignee_id (an agt_… id from GET /agents), or null to unassign it; the optional reason is kept in the ticket history.

6. Errors and rate limit

Errors follow RFC 9457 (application/problem+json, with type, title, status and detail):

  • 401: missing or invalid key;
  • 403: missing permission, or an organisation without access to the API;
  • 404: ticket or requester not found;
  • 409: the same request is still in progress;
  • 422: content refused (missing or invalid field);
  • 423: organisation in read-only mode;
  • 429: too many calls.

Each key has a limit of calls per minute. The RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers show it on every response; after a 429, wait for the delay given by Retry-After. Repeated attempts with invalid keys from the same source are blocked for a while.

7. Event notifications (webhooks)

On the same screen, subscribe an HTTPS address of your server to one or more events (10 active subscriptions at most):

  • ticket.created: a new ticket arrives, wherever it comes from (e-mail, widget, portal, agent or API);
  • ticket.updated: the status, priority, assignee or team of a ticket changes;
  • ticket.message_created: a new message (agent reply, internal note or requester reply).

Tikivio sends a JSON POST: id (evt_…, the same on every retry), type, created_at and data.ticket, the state of the ticket when it is sent. Answer with a 2xx code within 10 seconds; otherwise Tikivio retries 1 min, 5 min, 30 min, 2 h, 6 h and then 15 h later, then gives up. A private or internal address is refused. The latest deliveries are shown on the screen.

Each delivery carries the header X-Signature: t=<timestamp>,v1=<signature>: the signature is the hex HMAC-SHA256 of <t>.<raw body> with the subscription secret (whsec_…, shown only once). Check it before doing anything else:

$body = file_get_contents('php://input');
$parts = [];
foreach (explode(',', $_SERVER['HTTP_X_SIGNATURE'] ?? '') as $p) {
    [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
    $parts[$k] = $v;
}
$t = $parts['t'] ?? '';
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
if (!hash_equals($expected, $parts['v1'] ?? '') || abs(time() - (int) $t) > 300) {
    http_response_code(400);
    exit;
}

The same event can arrive twice: keep the id values you have already handled.

8. Good to know

  • An action made through the API shows in the ticket history under the name of the administrator who created the key, marked “via the API”.
  • Nothing can be deleted through the API in this version.
  • Version 1 only receives compatible additions (new fields, new calls): ignore fields you do not know.
  • Tickets are identified by their reference; other objects by opaque ids (ctc_…, agt_…), to be kept as they are.

Install the widget · Security and data: how-to