Skip to content
On this page

REST API reference

Tickets

Changes run through the same code as the app: notifications, SLA clocks, automation rules and webhooks all fire.

List tickets

GET/api/v1/tickets

List tickets the token's user can see.

Scope
tickets:read
Unscoped key
Read-only or read-write

Query parameters

statusstring
open, answered, on-hold or closed; comma-separate several.
prioritystring
low, normal, high, urgent or none; comma-separate several.
assignee_idstring
A user id, "me" or "unassigned".
department_idstring
Department id.
product_idinteger
Product id.
customer_idstring
Customer id.
customer_emailstring
The customer's exact email (case-insensitive). Works for every role, still limited to tickets the token's user can see.
tag_idstring
Tickets carrying this tag.
created_afterdatetime
ISO-8601 timestamp.
updated_afterdatetime
ISO-8601 timestamp; use it to sync changes.
searchstring
Case-insensitive match on the subject; a ticket number (1042 or #1042) also matches that ticket.
sortstring
created_at, -created_at, updated_at, -updated_at (default -created_at).
per_pageinteger
1-100, default 25.
cursorstring
meta.next_cursor from the previous page.
Request
curl https://zono.support/api/v1/tickets?status=open&priority=high,urgent&per_page=50 \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json"
Response200 OK
{
    "data": [
        {
            "object": "ticket",
            "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
            "number": 1042,
            "subject": "Cannot activate my license",
            "description": "<p>My key says it is already in use.</p>",
            "status": "answered",
            "priority": "high",
            "close_reason": null,
            "close_reason_detail": null,
            "customer": {
                "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
                "name": "Ada Lovelace",
                "email": "[email protected]"
            },
            "assignee": {
                "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
                "name": "Sam Agent",
                "email": "[email protected]"
            },
            "department": {
                "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
                "name": "Billing"
            },
            "product": {
                "id": 12,
                "name": "Zono Desktop"
            },
            "channel": null,
            "tags": [
                {
                    "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
                    "name": "licensing",
                    "color": "#6366f1"
                }
            ],
            "custom_fields": {
                "order_number": "A-1042"
            },
            "sla": null,
            "triage": null,
            "conversation_id": null,
            "spam": null,
            "merged_into": null,
            "created_at": "2026-01-15T09:58:12Z",
            "updated_at": "2026-01-15T10:24:07Z",
            "last_reply_at": "2026-01-15T10:24:07Z",
            "first_response_at": "2026-01-15T10:24:07Z",
            "closed_at": null,
            "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": "https://zono.support/api/v1/tickets?cursor=eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"
    },
    "meta": {
        "path": "https://zono.support/api/v1/tickets",
        "per_page": 25,
        "next_cursor": "eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9",
        "prev_cursor": null
    }
}

Get a ticket

GET/api/v1/tickets/{ticket}

One ticket with its customer, assignee, tags, custom fields and SLA summary.

Scope
tickets:read
Unscoped key
Read-only or read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.
Request
curl https://zono.support/api/v1/tickets/1042 \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json"
Response200 OK
{
    "object": "ticket",
    "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "number": 1042,
    "subject": "Cannot activate my license",
    "description": "<p>My key says it is already in use.</p>",
    "status": "answered",
    "priority": "high",
    "close_reason": null,
    "close_reason_detail": null,
    "customer": {
        "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
        "name": "Ada Lovelace",
        "email": "[email protected]"
    },
    "assignee": {
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent",
        "email": "[email protected]"
    },
    "department": {
        "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
        "name": "Billing"
    },
    "product": {
        "id": 12,
        "name": "Zono Desktop"
    },
    "channel": null,
    "tags": [
        {
            "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
            "name": "licensing",
            "color": "#6366f1"
        }
    ],
    "custom_fields": {
        "order_number": "A-1042"
    },
    "sla": null,
    "triage": null,
    "conversation_id": null,
    "spam": null,
    "merged_into": null,
    "created_at": "2026-01-15T09:58:12Z",
    "updated_at": "2026-01-15T10:24:07Z",
    "last_reply_at": "2026-01-15T10:24:07Z",
    "first_response_at": "2026-01-15T10:24:07Z",
    "closed_at": null,
    "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
}

Create a ticket

POST/api/v1/tickets

Create a ticket for an existing customer, or for a customer found (or created) by email.

Scope
tickets:write
Unscoped key
Read-write

Body parameters

subjectstringRequired
Up to 255 characters.
descriptionstringRequired
The first message; HTML is kept.
product_idintegerRequired
One of the team's products (GET /products).
customer_idstring
Required unless customer is given.
customerobject
{"email", "name", "external_id"}: found by email or created. Admin and owner tokens that also carry the customers:write scope (others get 403); agents pass customer_id.
prioritystring
low, normal, high or urgent.
department_idstring
Department id.
assignee_idstring
A team member who may work the ticket.
tag_idsarray
Tag ids (GET /tags).
custom_fieldsobject
Values keyed by field key. Required fields must be set.
Request
curl -X POST https://zono.support/api/v1/tickets \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"subject":"Cannot activate my license","description":"My key says it is already in use.","product_id":12,"customer":{"email":"[email protected]","name":"Ada Lovelace"},"priority":"high"}'
Response201 Created
{
    "object": "ticket",
    "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "number": 1042,
    "subject": "Cannot activate my license",
    "description": "<p>My key says it is already in use.</p>",
    "status": "answered",
    "priority": "high",
    "close_reason": null,
    "close_reason_detail": null,
    "customer": {
        "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
        "name": "Ada Lovelace",
        "email": "[email protected]"
    },
    "assignee": {
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent",
        "email": "[email protected]"
    },
    "department": {
        "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
        "name": "Billing"
    },
    "product": {
        "id": 12,
        "name": "Zono Desktop"
    },
    "channel": null,
    "tags": [
        {
            "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
            "name": "licensing",
            "color": "#6366f1"
        }
    ],
    "custom_fields": {
        "order_number": "A-1042"
    },
    "sla": null,
    "triage": null,
    "conversation_id": null,
    "spam": null,
    "merged_into": null,
    "created_at": "2026-01-15T09:58:12Z",
    "updated_at": "2026-01-15T10:24:07Z",
    "last_reply_at": "2026-01-15T10:24:07Z",
    "first_response_at": "2026-01-15T10:24:07Z",
    "closed_at": null,
    "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
}

Update a ticket

PATCH/api/v1/tickets/{ticket}

Change a ticket. Only the fields you send change. Merged tickets answer 409.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

subjectstring
New subject.
descriptionstring
New description.
statusstring
open, answered, on-hold or closed. Closing needs every required custom field.
close_reasonstring|null
With status closed: resolved (the default), not_resolved or spam.
close_reason_detailstring|null
With close_reason not_resolved: duplicate, no_reply, out_of_scope.
prioritystring|null
low, normal, high, urgent or null.
assignee_idstring|null
A team member, or null to unassign.
department_idstring|null
Department id or null.
product_idinteger
Product id.
tag_idsarray
Replaces the ticket's tags.
custom_fieldsobject
Values keyed by field key; null clears an optional field.
Request
curl -X PATCH https://zono.support/api/v1/tickets/1042 \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"status":"on-hold","priority":"urgent","custom_fields":{"order_number":"A-1042"}}'
Response200 OK
{
    "object": "ticket",
    "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "number": 1042,
    "subject": "Cannot activate my license",
    "description": "<p>My key says it is already in use.</p>",
    "status": "answered",
    "priority": "high",
    "close_reason": null,
    "close_reason_detail": null,
    "customer": {
        "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
        "name": "Ada Lovelace",
        "email": "[email protected]"
    },
    "assignee": {
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent",
        "email": "[email protected]"
    },
    "department": {
        "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
        "name": "Billing"
    },
    "product": {
        "id": 12,
        "name": "Zono Desktop"
    },
    "channel": null,
    "tags": [
        {
            "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
            "name": "licensing",
            "color": "#6366f1"
        }
    ],
    "custom_fields": {
        "order_number": "A-1042"
    },
    "sla": null,
    "triage": null,
    "conversation_id": null,
    "spam": null,
    "merged_into": null,
    "created_at": "2026-01-15T09:58:12Z",
    "updated_at": "2026-01-15T10:24:07Z",
    "last_reply_at": "2026-01-15T10:24:07Z",
    "first_response_at": "2026-01-15T10:24:07Z",
    "closed_at": null,
    "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
}

List a ticket's replies

GET/api/v1/tickets/{ticket}/replies

The ticket's replies, oldest first unless sorted, internal replies included.

Scope
tickets:read
Unscoped key
Read-only or read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Query parameters

sortstring
created_at (oldest first, default) or -created_at (newest first).
per_pageinteger
1-100, default 25.
cursorstring
meta.next_cursor from the previous page.
Request
curl https://zono.support/api/v1/tickets/1042/replies \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json"
Response200 OK
{
    "data": [
        {
            "object": "reply",
            "id": "01j9x3m4k7d2c8b5a6f0e1g2h4",
            "ticket_id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
            "body": "<p>Thanks Ada, a new key is on its way.</p>",
            "is_internal": false,
            "source": "web",
            "author": {
                "type": "user",
                "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
                "name": "Sam Agent"
            },
            "created_at": "2026-01-15T10:24:07Z"
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": "https://zono.support/api/v1/tickets/1042/replies?cursor=eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"
    },
    "meta": {
        "path": "https://zono.support/api/v1/tickets/1042/replies",
        "per_page": 25,
        "next_cursor": "eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9",
        "prev_cursor": null
    }
}

Reply to a ticket

POST/api/v1/tickets/{ticket}/replies

Reply as the token's user. Public replies are sent to the customer on the ticket's channel.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

bodystringRequired
The reply; HTML is kept.
is_internalboolean
Staff-only reply (default false).
close_ticketboolean
Close the ticket with this reply (default false).
Request
curl -X POST https://zono.support/api/v1/tickets/1042/replies \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"body":"Thanks Ada, a new key is on its way.","close_ticket":true}'
Response201 Created
{
    "object": "reply",
    "id": "01j9x3m4k7d2c8b5a6f0e1g2h4",
    "ticket_id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "body": "<p>Thanks Ada, a new key is on its way.</p>",
    "is_internal": false,
    "source": "web",
    "author": {
        "type": "user",
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent"
    },
    "created_at": "2026-01-15T10:24:07Z"
}

List a ticket's notes

GET/api/v1/tickets/{ticket}/notes

The ticket's internal notes, oldest first unless sorted.

Scope
tickets:read
Unscoped key
Read-only or read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Query parameters

sortstring
created_at (oldest first, default) or -created_at (newest first).
per_pageinteger
1-100, default 25.
cursorstring
meta.next_cursor from the previous page.
Request
curl https://zono.support/api/v1/tickets/1042/notes \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json"
Response200 OK
{
    "data": [
        {
            "object": "note",
            "id": "01j9x3n4o5t6e7a8b9c0d1e2f3",
            "ticket_id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
            "body": "Refund approved by finance.",
            "author": {
                "type": "user",
                "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
                "name": "Sam Agent"
            },
            "created_at": "2026-01-15T10:24:07Z",
            "updated_at": "2026-01-15T10:24:07Z"
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": "https://zono.support/api/v1/tickets/1042/notes?cursor=eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9"
    },
    "meta": {
        "path": "https://zono.support/api/v1/tickets/1042/notes",
        "per_page": 25,
        "next_cursor": "eyJpZCI6IjAxajl4MnAzcTQiLCJfcG9pbnRzVG9OZXh0SXRlbXMiOnRydWV9",
        "prev_cursor": null
    }
}

Add a note to a ticket

POST/api/v1/tickets/{ticket}/notes

Add an internal note as the token's user.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

bodystringRequired
The note.
Request
curl -X POST https://zono.support/api/v1/tickets/1042/notes \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"body":"Refund approved by finance."}'
Response201 Created
{
    "object": "note",
    "id": "01j9x3n4o5t6e7a8b9c0d1e2f3",
    "ticket_id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "body": "Refund approved by finance.",
    "author": {
        "type": "user",
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent"
    },
    "created_at": "2026-01-15T10:24:07Z",
    "updated_at": "2026-01-15T10:24:07Z"
}

Mark a ticket as spam

POST/api/v1/tickets/{ticket}/spam

Mark the ticket as spam, as the ticket page does: it leaves the app's ticket lists, its SLA clocks and survey stop, and it is deleted for good after 30 days unless moved out of spam (the ticket's spam field shows when). Marking a spam ticket again changes nothing. meta tells how many tickets were marked and what was blocked or skipped. Merged tickets answer 409.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

blockarray
Also block the sender by any of: email, domain (not free email providers), ip (live chat visitors), customer. Types the sender has no value for are skipped and listed in meta.skipped.
include_other_openboolean
Also mark the customer's other open tickets (those you can access) as spam.
Request
curl -X POST https://zono.support/api/v1/tickets/1042/spam \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"block":["email"]}'
Response200 OK
{
    "object": "ticket",
    "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "number": 1042,
    "subject": "Cannot activate my license",
    "description": "<p>My key says it is already in use.</p>",
    "status": "answered",
    "priority": "high",
    "close_reason": null,
    "close_reason_detail": null,
    "customer": {
        "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
        "name": "Ada Lovelace",
        "email": "[email protected]"
    },
    "assignee": {
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent",
        "email": "[email protected]"
    },
    "department": {
        "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
        "name": "Billing"
    },
    "product": {
        "id": 12,
        "name": "Zono Desktop"
    },
    "channel": null,
    "tags": [
        {
            "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
            "name": "licensing",
            "color": "#6366f1"
        }
    ],
    "custom_fields": {
        "order_number": "A-1042"
    },
    "sla": null,
    "triage": null,
    "conversation_id": null,
    "spam": null,
    "merged_into": null,
    "created_at": "2026-01-15T09:58:12Z",
    "updated_at": "2026-01-15T10:24:07Z",
    "last_reply_at": "2026-01-15T10:24:07Z",
    "first_response_at": "2026-01-15T10:24:07Z",
    "closed_at": null,
    "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
}

Move a ticket out of spam

DELETE/api/v1/tickets/{ticket}/spam

Move the ticket out of spam: it is back in the app's lists and its SLA clocks resume. meta.unmarked is false when it was not spam.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

remove_blocksboolean
Also lift the blocks that match the sender. Owners and admins only (403 otherwise).
Request
curl -X DELETE https://zono.support/api/v1/tickets/1042/spam \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"remove_blocks":true}'
Response200 OK
{
    "object": "ticket",
    "id": "01j9x2p3q4r5s6t7u8v9w0x1y2",
    "number": 1042,
    "subject": "Cannot activate my license",
    "description": "<p>My key says it is already in use.</p>",
    "status": "answered",
    "priority": "high",
    "close_reason": null,
    "close_reason_detail": null,
    "customer": {
        "id": "01j9x1c2d3e4f5g6h7j8k9m0n1",
        "name": "Ada Lovelace",
        "email": "[email protected]"
    },
    "assignee": {
        "id": "01j9x0u1v2w3x4y5z6a7b8c9d0",
        "name": "Sam Agent",
        "email": "[email protected]"
    },
    "department": {
        "id": "01j9x0d1e2p3t4a5b6c7d8e9f0",
        "name": "Billing"
    },
    "product": {
        "id": 12,
        "name": "Zono Desktop"
    },
    "channel": null,
    "tags": [
        {
            "id": "01j9x0t1a2g3b4c5d6e7f8g9h0",
            "name": "licensing",
            "color": "#6366f1"
        }
    ],
    "custom_fields": {
        "order_number": "A-1042"
    },
    "sla": null,
    "triage": null,
    "conversation_id": null,
    "spam": null,
    "merged_into": null,
    "created_at": "2026-01-15T09:58:12Z",
    "updated_at": "2026-01-15T10:24:07Z",
    "last_reply_at": "2026-01-15T10:24:07Z",
    "first_response_at": "2026-01-15T10:24:07Z",
    "closed_at": null,
    "url": "https://zono.support/tickets/01j9x2p3q4r5s6t7u8v9w0x1y2"
}

Set a ticket's metadata

PATCH/api/v1/tickets/{ticket}/metadata

Set metadata about the ticket, shown to your team under From your app. A ticket that came from a chat keeps it on its conversation, so the chat shows the same values. Keys are 1 to 40 lowercase letters, digits and underscores (zono_ is reserved; keys naming passwords, secrets, tokens, cards, CVV, SSN or IBAN are refused). Values are strings of at most 255 characters (ISO dates as strings), numbers or booleans; null deletes the key. At most 30 keys are kept and 4 KB sent per request; the latest write of a key wins. Strings that look like a card number or an IBAN are stored as [removed]. Values written here are marked Signed in the inbox and are never sent to the AI agent.

Scope
tickets:write
Unscoped key
Read-write

Path parameters

ticketstringRequired
Ticket ULID or ticket number.

Body parameters

metadataobjectRequired
Keys and values to set; null removes a key.
Request
curl -X PATCH https://zono.support/api/v1/tickets/1042/metadata \
  -H "Authorization: Bearer $ZONO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"metadata":{"order_id":"A-1042","refund_eligible":true}}'
Response200 OK
{
    "object": "conversation",
    "id": "01j9x0a1b2c3d4e5f6g7h8j9k0",
    "metadata": {}
}