All endpoints return JSON. Errors share one shape:
{ "message": "...", "errors": { ... } }.
GET
/statistics/support
scope: statistics:read
Aggregate support reporting for the key’s company or brand. Includes current and previous summaries, current backlog, daily counts, topics and channels. Maximum 90 local calendar days. Spam and deleted tickets are excluded. Durations are medians for tickets created in the period, including AI first replies. Resolved counts use resolution dates and exclude reopened tickets. AI-only resolutions require a confirmed automatic reply with no public human reply. The previous period has the same local-day span and equivalent cutoff time. Null durations mean no qualifying observations.
Parameters
| Name | In | Type | Description |
| from |
query |
date |
Required first date, YYYY-MM-DD (inclusive). |
| to |
query |
date |
Required last date, YYYY-MM-DD (inclusive), no future dates. |
| timezone |
query |
string |
IANA timezone, defaults to UTC. Brand scope comes from the key; brand_id is rejected. |
Request
GET https://app.kvix.ai/api/v1/statistics/support?from=2026-09-01&to=2026-09-30&timezone=Europe%2FCopenhagen (X-API-Key header)
Response
{"data":{"version":1,"brand":{"id":2,"name":"All2day"},"summary":{"created_tickets":100,"resolved_tickets":90,"ai_only_resolved_tickets":40,"ai_only_resolution_percent":40,"median_first_response_seconds":120,"first_response_sample_count":80,"median_resolution_seconds":600,"resolution_sample_count":70},"backlog":{"total":12,"unanswered":5,"by_status":{"new":5,"open":4,"pending":3},"by_age":{"under_24_hours":6,"24_to_72_hours":4,"over_72_hours":2},"oldest_seconds":300000},"daily":[],"topics":[],"channels":[]}}
GET
/me
scope: any valid key
Describes the calling key: company, brand, scopes and rate limit. Use it to verify an integration's configuration without touching data.
Request
curl -H "X-API-Key: $KVIX_KEY" https://app.kvix.ai/api/v1/me
Response
{
"data": {
"key": { "name": "Production server", "last_four": "tv96", "scopes": ["tickets:read", "messages:read"], "all_access": false, "expires_at": null },
"company": { "id": 1, "name": "ApexX" },
"brand": { "id": 2, "name": "All2day" },
"rate_limit_per_minute": 120
}
}
GET
/tickets
scope: tickets:read
Paginated list of tickets with customer and brand, newest activity first. All filters are optional and combined with AND.
Parameters
| Name | In | Type | Description |
| status |
query |
string |
Filter by status (new, open, pending, resolved, closed, spam). |
| priority |
query |
string |
low, normal, high or urgent. |
| channel |
query |
string |
email, widget, form, api, manual, import, facebook or instagram. |
| assigned_to |
query |
string |
A member id, or "none" for unassigned tickets. |
| tag |
query |
string |
Tickets carrying this tag. |
| customer_email |
query |
string |
Tickets of the customer with exactly this e-mail address. |
| q |
query |
string |
Free text (2–100 chars) matched against subject, reference code, customer name and e-mail. |
| updated_since |
query |
datetime |
Only tickets with activity at or after this ISO-8601 time. Ideal for incremental polling. |
| created_since |
query |
datetime |
Only tickets created at or after this ISO-8601 time. |
| sort |
query |
string |
-last_activity_at (default), last_activity_at, -created_at or created_at. A leading minus means newest first. |
| per_page |
query |
integer |
Items per page (1–100, default 25). |
| page |
query |
integer |
Page number (default 1). |
Request
curl -H "X-API-Key: $KVIX_KEY" \
https://app.kvix.ai/api/v1/tickets?status=open&per_page=10
Response
{
"data": [
{
"id": 18,
"reference_code": "TKT-AC-00018",
"subject": "Refund question",
"status": "open",
"priority": "normal",
"channel": "email",
"assigned_to": null,
"tags": ["billing"],
"first_response_at": null,
"resolved_at": null,
"closed_at": null,
"last_activity_at": "2026-05-15T09:14:02+00:00",
"created_at": "2026-05-15T08:55:11+00:00"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "per_page": 10, "total": 28 }
}
POST
/tickets
scope: tickets:write
Create a new ticket. Customer is matched by email or auto-created.
Parameters
| Name | In | Type | Description |
| email |
body |
string |
Customer email (required). |
| subject |
body |
string |
Ticket subject (required). |
| body |
body |
string |
Opening message body (required). |
| name |
body |
string |
Customer display name (optional). |
| brand_id |
body |
integer |
Target brand id. Required for company-wide keys when the company has no default brand. |
Request
curl -H "X-API-Key: $KVIX_KEY" \
-H 'Content-Type: application/json' \
-X POST https://app.kvix.ai/api/v1/tickets \
-d '{"email":"[email protected]","subject":"Hello","body":"My order is late."}'
Response
{
"data": {
"id": 142,
"reference_code": "TKT-AC-00142",
"subject": "Hello",
"status": "new",
"channel": "api",
"customer": { "id": 88, "email": "[email protected]", "name": null },
"brand": { "id": 3, "slug": "default", "name": "Acme Support" }
}
}
GET
/tickets/{id}
scope: tickets:read
A single ticket plus its full message thread.
Parameters
| Name | In | Type | Description |
| id |
path |
integer |
The ticket id. |
Request
curl -H "X-API-Key: $KVIX_KEY" https://app.kvix.ai/api/v1/tickets/142
Response
{
"data": {
"id": 142,
"subject": "Hello",
"status": "open",
"messages": [
{ "id": 401, "sender_type": "customer", "body": "My order is late." },
{ "id": 402, "sender_type": "member", "body": "Sorry — let me check." }
]
}
}
PATCH
/tickets/{id}
scope: tickets:write
Update one or more of: status, priority, assignee, tags. Returns the refreshed ticket.
Parameters
| Name | In | Type | Description |
| status |
body |
string |
One of: new, open, pending, resolved, closed, spam. Moving to resolved/closed timestamps the ticket. |
| priority |
body |
string |
One of: low, normal, high, urgent. |
| assigned_to |
body |
integer|null |
User id of a member in your company, or null to unassign. |
| tags |
body |
array |
Array of tag strings; replaces the existing set. |
Request
curl -H "X-API-Key: $KVIX_KEY" \
-H 'Content-Type: application/json' \
-X PATCH https://app.kvix.ai/api/v1/tickets/142 \
-d '{"status":"resolved"}'
Response
{
"data": {
"id": 142,
"status": "resolved",
"resolved_at": "2026-05-15T10:02:18+00:00"
}
}
GET
/tickets/{id}/messages
scope: messages:read
Paginated message list for a ticket, oldest first.
Parameters
| Name | In | Type | Description |
| id |
path |
integer |
The ticket id. |
| per_page |
query |
integer |
Items per page (1–200, default 50). |
Request
curl -H "X-API-Key: $KVIX_KEY" https://app.kvix.ai/api/v1/tickets/142/messages
Response
{
"data": [
{ "id": 401, "sender_type": "customer", "body": "My order is late.", "is_internal_note": false },
{ "id": 402, "sender_type": "member", "body": "Sorry — let me check.", "is_internal_note": false }
],
"meta": { "current_page": 1, "per_page": 50, "total": 2 }
}
POST
/tickets/{id}/messages
scope: messages:write
Append a member reply or internal note. A reply is delivered to the customer on the ticket's channel (e-mail, Facebook, Instagram), stamps the first response time, moves a new ticket to open and reopens a resolved one. Internal notes are never sent and never change the status.
Parameters
| Name | In | Type | Description |
| body |
body |
string |
Message body (required, max 65535 chars). Facebook and Instagram replies must be plain text up to 2,000 characters inside the 24-hour window, otherwise the request fails with 422. |
| is_internal_note |
body |
boolean |
Default false. Set true to add a note visible only to members. |
| deliver |
body |
boolean |
Default true. Set false to store a reply without sending it, e.g. when importing a conversation that happened elsewhere. Ignored for internal notes. |
Request
curl -H "X-API-Key: $KVIX_KEY" \
-H 'Content-Type: application/json' \
-X POST https://app.kvix.ai/api/v1/tickets/142/messages \
-d '{"body":"Thanks for your patience — refund issued."}'
Response
{
"data": {
"id": 403,
"ticket_id": 142,
"sender_type": "member",
"body": "Thanks for your patience — refund issued.",
"is_internal_note": false,
"ai_generated": false,
"created_at": "2026-05-15T10:05:01+00:00"
}
}
GET
/reviews
scope: reviews:read
Paginated list of reviews, newest first. Read-only: replying to a review stays a dashboard action. The author's e-mail and internal AI fields are never returned.
Parameters
| Name | In | Type | Description |
| status |
query |
string |
unread, open, replied, ignored or flagged. |
| provider |
query |
string |
Review source, e.g. trustpilot or google_business_profile. |
| rating |
query |
integer |
Exactly this many stars (1–5). |
| min_rating |
query |
integer |
At least this many stars (1–5). |
| max_rating |
query |
integer |
At most this many stars (1–5). max_rating=2 finds the reviews that need attention. |
| sentiment |
query |
string |
Sentiment label assigned during analysis. |
| unanswered |
query |
boolean |
true for reviews without a reply, false for replied ones. |
| posted_since |
query |
datetime |
Only reviews posted at or after this ISO-8601 time. |
| q |
query |
string |
Free text (2–100 chars) matched against title, body and author name. |
| sort |
query |
string |
-posted_at (default), posted_at, -rating or rating. A leading minus means descending. |
| per_page |
query |
integer |
Items per page (1–100, default 25). |
Request
curl -H "X-API-Key: $KVIX_KEY" \
"https://app.kvix.ai/api/v1/reviews?max_rating=2&unanswered=true"
Response
{
"data": [
{
"id": 88,
"provider": "trustpilot",
"external_url": "https://www.trustpilot.com/reviews/…",
"author_name": "Karen K.",
"rating": 1,
"title": "Voucher did not work",
"body": "…",
"language": "da",
"sentiment": "negative",
"category": null,
"tags": [],
"status": "unread",
"brand": { "id": 2, "name": "All2day" },
"posted_at": "2026-09-20T18:04:11+00:00",
"edited_at": null,
"replied_at": null
}
],
"links": { "first": "…", "last": "…", "prev": null, "next": null },
"meta": { "current_page": 1, "per_page": 25, "total": 1 }
}
GET
/reviews/{id}
scope: reviews:read
A single review with the replies that were actually posted (drafts and pending approvals are internal).
Parameters
| Name | In | Type | Description |
| id |
path |
integer |
Review id. |
Request
curl -H "X-API-Key: $KVIX_KEY" https://app.kvix.ai/api/v1/reviews/88
Response
{
"data": {
"id": 88,
"provider": "trustpilot",
"rating": 1,
"title": "Voucher did not work",
"status": "replied",
"replies": [
{ "id": 12, "body": "We are sorry — a new voucher is on its way.", "ai_generated": false, "posted_at": "2026-09-21T08:12:40+00:00" }
]
}
}