Tickets API
Create and manage support tickets via the API
The Tickets API allows you to create, retrieve, update, and list support tickets programmatically. Request bodies reject fields that are not listed for the relevant endpoint rather than silently ignoring them.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/tickets | List tickets |
| POST | /api/v1/tickets | Create a ticket |
| GET | /api/v1/tickets/:id | Get a ticket |
| PATCH | /api/v1/tickets/:id | Update a ticket |
| GET | /api/v1/tickets/:id/messages | List messages |
| POST | /api/v1/tickets/:id/messages | Add a message |
List Tickets
Retrieve a paginated list of tickets.
GET /api/v1/ticketsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
page | positive integer | Page number (default: 1) |
per_page | positive integer | Items per page (max: 100, default: 20) |
status | string | Filter by status: open, pending, escalated, solved, closed |
workspace_id | string | Must match the workspace bound to the API key |
Example Request
curl "https://app.keva.support/api/v1/tickets?status=open&per_page=10" \
-H "Authorization: Bearer keva_live_your_api_key"Example Response
{
"data": [
{
"id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"subject": "Cannot access my account",
"status": "open",
"priority": "high",
"customerEmail": "customer@example.com",
"customerName": "John Doe",
"source": "email",
"assignedTo": null,
"resolutionType": null,
"tags": ["login", "urgent"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"page": 1,
"perPage": 10,
"total": 45
}
}Ticket responses are limited to the fields shown above. Internal workspace custody, provider identifiers, confirmation state, presence, and channel-thread metadata are not part of the public response contract.
Create Ticket
Create a new support ticket.
POST /api/v1/ticketsRequest Body
The complete JSON request body is limited to 1 MiB (1,048,576 bytes). Requests
over this limit return 413 Payload Too Large.
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | Yes | Ticket subject line (1-500 characters) |
customerEmail | string | Yes | Customer's email address (maximum 320 characters) |
customerName | string | No | Customer's name (max 200 characters) |
body | string | Yes | Initial message content (1-100,000 characters) |
source | string | No | email, widget, or api (default: api) |
language | string | No | Two- or three-letter lowercase language code, such as en |
Example Request
curl https://app.keva.support/api/v1/tickets \
-H "Authorization: Bearer keva_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "Order not delivered",
"customerEmail": "customer@example.com",
"customerName": "Jane Smith",
"body": "My order #12345 was supposed to arrive yesterday but I havent received it yet.",
"language": "en"
}'Example Response
{
"data": {
"id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"subject": "Order not delivered",
"status": "open",
"priority": "medium",
"customerEmail": "customer@example.com",
"customerName": "Jane Smith",
"source": "api",
"createdAt": "2024-01-15T14:22:00Z"
}
}Get Ticket
Retrieve a single ticket by ID.
GET /api/v1/tickets/:idExample Request
curl https://app.keva.support/api/v1/tickets/0f8fad5b-d9cb-469f-a165-70867728950e \
-H "Authorization: Bearer keva_live_your_api_key"Update Ticket
Update ticket properties.
PATCH /api/v1/tickets/:idRequest Body
The complete JSON request body is limited to 1 MiB (1,048,576 bytes). Requests
over this limit return 413 Payload Too Large.
| Field | Type | Description |
|---|---|---|
status | string | open, pending, escalated, solved, closed |
priority | string | low, medium, high, urgent |
assignedTo | string | User ID to assign (max 200 characters); must be a current member or admin in the API key's exact workspace. The ai sentinel is also allowed. Blank, whitespace-only, and null values are invalid. |
At least one supported field must be present in an update request.
Assigning a human user is limited to current members and admins of the exact
workspace bound to the API key. Blank, whitespace-only, or null assignees are
malformed and return 400; a syntactically valid human ID that is not a current
member or admin of that exact workspace returns 403.
Example Request
curl -X PATCH https://app.keva.support/api/v1/tickets/0f8fad5b-d9cb-469f-a165-70867728950e \
-H "Authorization: Bearer keva_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"status": "pending", "priority": "high"}'List Messages
Get messages for a ticket. By default, the newest 100 messages are returned; limit may be set from 1 to 200. Results are returned oldest-first within the selected window. The response includes total (the true count) and truncated (whether older messages were omitted). Use before with a message id or ISO timestamp to select an older window; a message-id cursor must belong to this ticket, otherwise the API returns 400.
GET /api/v1/tickets/:id/messages?limit=100&before=2024-01-15T10:30:00ZExample Response
{
"data": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"ticketId": "0f8fad5b-d9cb-469f-a165-70867728950e",
"direction": "inbound",
"senderEmail": "customer@example.com",
"senderName": "John Doe",
"body": "I need help with my order",
"bodyHtml": null,
"createdAt": "2024-01-15T10:30:00Z"
},
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae8",
"ticketId": "0f8fad5b-d9cb-469f-a165-70867728950e",
"direction": "outbound",
"senderEmail": "support@company.com",
"senderName": null,
"body": "Happy to help! Let me look into this.",
"bodyHtml": null,
"createdAt": "2024-01-15T10:31:00Z"
}
],
"total": 2,
"truncated": false
}An invalid limit or malformed/out-of-range before returns 400.
Message responses are limited to the fields shown above. Provider IDs and email-thread headers are not returned by the public API.
Add Message
Add a message to an existing ticket.
POST /api/v1/tickets/:id/messagesRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
body | string | Yes | Message content (1-100,000 characters) |
senderEmail | string | Yes | Sender's email (maximum 320 characters) |
senderName | string | No | Sender's name (max 200 characters) |
direction | string | No | inbound or outbound (default: inbound) |
bodyHtml | string or null | No | HTML version of message (max 200,000 characters) |
The full JSON request body is limited to 2 MiB (2,097,152 bytes). Requests
that exceed this limit return 413 Payload Too Large.
Inbound messages are the supported public write path. Direct public outbound
delivery remains unavailable; a request with direction: "outbound" returns
409 Conflict until it is routed through approval and delivery custody.
An inbound message counts as a customer reply only when senderEmail matches the
ticket's customer email (ignoring case and surrounding spaces). A matching inbound
sender emits the registered TICKET_MESSAGE_ADDED acceptance event. That event may
start configured follow-up/agent/workflow handling and downstream reply or approval
behavior, including "message received" workflows and reopening a pending or solved
ticket. A mismatched sender emits no event; the message is saved on the ticket but
starts nothing.
The 201 Created response says which happened in meta.messageAddedEvent:
| Value | Meaning |
|---|---|
emitted | The message was treated as a customer reply |
skipped_sender_mismatch | senderEmail did not match the ticket's customer; the message was saved only |
Example Request
curl -X POST https://app.keva.support/api/v1/tickets/0f8fad5b-d9cb-469f-a165-70867728950e/messages \
-H "Authorization: Bearer keva_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"body": "Any update on this?",
"senderEmail": "customer@example.com",
"direction": "inbound"
}'