Reply to ticket API
This article and the rest of the API documentation in this section are written for a technical audience — integrators and developers connecting external systems to Tickiti. Familiarity with HTTP, REST, JSON and bearer-token authentication is assumed.
The reply endpoint posts a response onto an existing ticket and, in the same call, can change the ticket itself — its status, hold date, assignee, priority, resolution, queue, subject and participants. Use it when an external system has new information to add to a conversation Tickiti is already tracking — for example an order system posting a shipping update, or a CRM adding a note when a customer record changes — or when it needs to move the ticket along.
Endpoint
POST /api/v1/tickets/respondTwo shortcuts go through the same endpoint and return the same response: close a ticket and add or remove participants.
Headers
Authorization: Bearer YOUR_TOKEN— required. Token must have thetickets:writeability.Idempotency-Key: <uuid>— required. See Idempotency.Content-Type: application/json— required.
Body
Required fields:
ticket_number— the 6-digit number of the ticket to respond to, as a string or a JSON number. Alternatively sendticket_id, the ticket’s internal id. Send exactly one of the two.from_email— the email address the response is attributed to. If this address is not already a participant on the ticket it is added as one.content— the response body (string, HTML allowed). Required unless the request carries a ticket change (any of the attribute fields below), in which case the change is recorded as a response with no body.
The body is always sent as content. When content is empty and the request carries html, body, message, text, reply, response, note or content_html, the request is refused with 422 validation_failed naming that parameter, so a body sent under the wrong name is never lost. Any other parameter the endpoint does not recognise is returned in data.ignored_params, so a misspelt field is visible in the result.
Optional fields:
is_internal— boolean, default false. When true the response is a staff-only internal note and is not shown to the customer or included in customer notifications.attachments— an array of{ "sha256": ..., "name": ..., "file_size": ... }objects returned byPOST /api/v1/tickets/attachment-upload, attached to the response as regular (non-inline) attachments. See Create ticket API — Attaching files.send_full_email— boolean, default false. Email customer participants the reply itself, in the full conversation format, instead of the short notification with a link. Staff notifications are unchanged, and it has no effect on an internal note.data.full_email_sentconfirms it.keep_status— boolean, default false. Leave the ticket’s status as it is even for a public reply. See Status and reopening.
Ticket changes, applied together with the response (each optional):
status—open,on-holdorclosed.openandclosedclear any hold date.on_hold_until— the date the hold ends (YYYY-MM-DD). Required withstatus: on-hold; sent on its own it puts the ticket on hold until that date.assigned_to_email— a staff member’s email, a team’s keyteam:<id>(list the keys withPOST /api/v1/tickets/teams) orUnassigned. The assignee — for a team, every member — becomes a participant.priority— a code"10","20","30","40"or a nameLow,Normal,High,Urgent(case-insensitive), sent as a string.resolved— a resolution category, by id or by name. List the categories with the resolutions lookup in the Ticket operations API.queue— a queue name; moves the ticket to that queue.subject— renames the ticket.add_participants— an array of email addresses to add as participants. Each person added is notified that they were added, even on an internal note.remove_participants— an array of email addresses to remove. The originator always stays on the ticket. People removed are not notified of the response that records their removal.
Every change is recorded on the response it arrived with, in the response’s audit (see Show ticket API), exactly as a change made on the ticket page is.
Status and reopening
- With
status(oron_hold_until) supplied, Tickiti applies it as given. - With no status supplied, a public reply with a body reopens a closed or on-hold ticket and clears its hold date — it is a reply the customer will answer.
data.reopenedis true when this happens. Add"keep_status": trueto leave the status alone. - An internal note, or a request that only changes attributes or participants, leaves the status and any hold date as they were.
- A response attributed to a non-staff
from_emailis handled as a reply from that customer: it reopens a closed or on-hold ticket (other than a task), and if the ticket has no staff participant Tickiti routes it to the inbox queue as incoming customer mail.
Closing and resolutions
On a queue that tracks resolutions, closing a ticket that has no resolution yet requires resolved in the same request; without it the request is refused with 422 validation_failed and an errors.resolved message. When the request also moves the ticket with queue, the destination queue’s setting applies. This holds for status: closed here and for /tickets/close.
@tags and #hashtags
In a response from a staff author, @tags and #hashtags work as described for Create ticket: a tag that resolves to nobody (or to several people) is kept as plain text and reported in data.tag_warnings, a hashtag resembling an existing one is reported in data.hashtag_warnings, and an @ or # inside a code block is not a tag.
Inline images
To embed an image in the response, include it in content as a base64 data URI — an <img src="data:image/png;base64,…"> element placed wherever the image should appear in the body. Tickiti decodes each such image, stores it as an inline attachment, and rewrites the body to reference it, so it renders within the response thread and in customer emails.
Examples
A public reply:
curl -X POST https://support.sole-provider.example/api/v1/tickets/respond \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"ticket_number": "220009",
"from_email": "[email protected]",
"content": "<p>Your replacement Vapor Pro 3 has shipped. Tracking: TRACK-XYZ.</p>"
}'An internal note that hands the ticket to a team, raises its priority and parks it until a date:
{
"ticket_number": 220009,
"from_email": "[email protected]",
"content": "<p>Waiting on the courier — warehouse team to follow up.</p>",
"is_internal": true,
"assigned_to_email": "team:3",
"priority": "High",
"on_hold_until": "2026-05-08"
}A change with no body — move the ticket and rename it:
{
"ticket_number": "220009",
"from_email": "[email protected]",
"queue": "Warranty",
"subject": "Vapor Pro 3 plate failure — warranty claim"
}Successful response
{
"ok": true,
"data": {
"ticket_id": 1,
"response_id": 42,
"reopened": true,
"status": "open",
"on_hold_until": null,
"priority": "20",
"assigned_to_email": "[email protected]",
"resolved": null,
"queue_id": 4,
"queue": "Returns",
"subject": "Carbon plate cracked on first marathon",
"is_internal": false,
"full_email_sent": false,
"tag_warnings": [],
"hashtag_warnings": [],
"ignored_params": []
}
}response_id— the response that was written (for a body-less change, the response that records it).reopened— true when a public reply reopened a closed or on-hold ticket (see Status and reopening). An explicitstatus: openreports false; readstatusfor the result.status,on_hold_until,priority,assigned_to_email,resolved,queue_id,queue,subject— the ticket’s values after the request.is_internal— whether the response is an internal note.full_email_sent— true whensend_full_emailapplied.tag_warnings,hashtag_warnings— tags posted as text; see @tags and #hashtags.ignored_params— parameters in the request that the endpoint does not use.
Close a ticket
POST /api/v1/tickets/closeCloses a ticket, optionally with a closing response and a resolution. Same headers as /tickets/respond (tickets:write, Idempotency-Key). The body takes the ticket reference (ticket_number or ticket_id) and optionally from_email (defaults to the token’s owner), content, is_internal and resolved. The rules in Closing and resolutions apply, and the response is the same as for /tickets/respond.
{ "ticket_number": "220009", "resolved": "Replaced under warranty" }Add or remove participants
POST /api/v1/tickets/participants/add
POST /api/v1/tickets/participants/removeChanges a ticket’s participants without composing a message. Same headers as /tickets/respond. The body takes the ticket reference, participants — a non-empty array of email addresses — and optionally from_email (defaults to the token’s owner). The change is recorded in the thread as a response with no body, and the response is the same as for /tickets/respond. People added are notified that they were added; the originator cannot be removed.
{ "ticket_number": "220009", "participants": ["[email protected]"] }Error responses
Every error has "ok": false and an error code; validation errors list the problems per field under errors.
422 validation_failed— missingfrom_email; no body and no change; an unknown ticket number; both or neither ofticket_numberandticket_id; a body sent under another name;on-holdwithout a date; an unknown priority, resolution, queue or team key; a close that needs a resolution; an emptyparticipantslist; or a missingIdempotency-Key.404 not_found— no ticket has the giventicket_id.409 idempotency_key_conflict— the idempotency key was already used with a different payload. See Idempotency.401 unauthenticated— bad or missing token.403 forbidden— token does not have thetickets:writeability, or the user it acts as cannot see the ticket (they must be its assignee, a participant, a member of its assigned team, or able to view its queue). A token owned by an API account, a system administrator or the system user may act on any ticket. This check runs before the idempotency key is recorded, so the same key can be retried once access is granted.
What happens server-side
- Tickiti locates the ticket by
ticket_numberorticket_idand validates the request. - It checks that the token’s owner may act on the ticket.
- It applies the requested status, hold date, priority, assignee, resolution, queue and subject, or reopens the ticket under the rules in Status and reopening.
- It adds
from_emailas a participant if it was not one already. - It writes the response, public or internal per
is_internal, attributed tofrom_email, adding anyadd_participants, resolving @tags and #hashtags, and extracting base64 data-URI images into inline attachments. - It sends the notifications (the full reply instead of the short notification when
send_full_emailis set), then removes anyremove_participants. - It records every change in the response’s audit, attaches any uploaded
attachments, and refreshes the affected ticket-list views.
To read the conversation before or after replying, use the Show ticket API. To edit or delete a response, read a single response by id, or look up staff and resolution categories, see the Ticket operations API. See the API overview for the shared conventions.