1. Create a key
An organisation administrator creates the key in Settings › API (/o/<your-organisation>/parametres/api) and chooses its permissions:
| Permission | What it allows |
|---|---|
tickets:read | see tickets, their messages and the list of agents |
tickets:write | create tickets, change their status, priority or assignee, and write messages |
contacts:read | see requesters (name and e-mail address) |
contacts:write | add 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
| Call | Purpose | Permission |
|---|---|---|
GET /tickets | list tickets, most recent first; filters status (comma-separated values) and updated_since | tickets:read |
POST /tickets | create a ticket | tickets:write |
GET /tickets/{reference} | one ticket, by its reference (for example TIKI-000123) | tickets:read |
PATCH /tickets/{reference} | change the status, priority or assignee | tickets:write |
GET /tickets/{reference}/messages | the messages of a ticket | tickets:read |
POST /tickets/{reference}/messages | add a public reply or an internal note | tickets:write |
GET /contacts | requesters; filter email | contacts:read |
POST /contacts | add a requester, or find the one who already has this address | contacts:write |
GET /contacts/{id} | one requester | contacts:read |
GET /agents | active agents, to assign a ticket | tickets:read |
GET /organisation | the 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.