Open Approval Card
POST/api/support/agent/tickets/:ticket/approvals
Opens an approval card on a ticket: a proposal a human must decide before the AI agent acts. A ticket has at most one open card — any card still pending or snoozed on the ticket is marked superseded (reason new_card). The new card gets the next per-ticket version. The ticket's owner gets a portal alert (and a push notification on their installed app) when they are a human; otherwise every agent who can decide cards does. No email is sent. Once the card is decided or superseded, its alerts are marked read for everyone. An internal approval_requested event is recorded.
support:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
A reply card's payload.draft_body is also written into the ticket's shared reply draft so it is ready to send — unless a human is already holding that draft, whose words are never overwritten.
The AI agent may only open a card on a ticket it owns — otherwise 409 not_owner. Any human agent may open one on any ticket.
Path parameters
ticket(integer) — the ticket id.
Request body
kind(string, required) — one of: reply, question, fix, merge, repair, status, escalate, errortitle(string, required, max 160)body(string, required, max 20000) — the proposal in plain text. Attach long reports and link them fromevidence.confidence(string, optional, nullable) — high, medium or lowpayload(object, optional, nullable) — what the card proposes:payload.draft_body(string, max 65000) — required whenkindisreply: the reply to sendpayload.status_after(string, nullable) — the status to set once the reply is sent: open, in_progress, waiting_on_customer, awaiting_release, resolved, closedpayload.cc[](array of email addresses, max 20) — extra recipients of the replypayload.choices[](array, 1–10) — required whenkindisquestion; each{key (string, required, max 40, unique), label (string, required, max 500), recommended (boolean, optional)}payload.allow_text(boolean, nullable) — a question card may also be answered in free textpayload.status(string) — required whenkindisstatus: the status it proposes (open, in_progress, waiting_on_customer, awaiting_release, resolved, closed)
evidence[](array, optional, max 50) — each{label (string, required, max 200), url (string, nullable, max 2000), attachment_id (integer, nullable)}files[](array, optional, max 10) — files the card carries: a report, HTML mockups, screenshots, a PDF. Each{filename (string, required, max 255), content_type (string, required), content_base64 (string, required — the file's bytes, base64-encoded), label (string, optional, max 200 — defaults to the filename)}.content_typemust be one of: text/html, image/png, image/jpeg, image/gif, image/webp, application/pdf, text/plain, text/markdown, application/json, text/csv. Each file may be at most 10 MB decoded, and all of a card's files at most 25 MB together. Invalid base64, a type outside that list, or a size over either limit is a 422 and nothing is stored. Files are internal to the support desk: they are never shown to, or downloadable by, the customer. Each file is appended to the card'sevidence(after anyevidence[]you send) as{label, attachment_id, filename, content_type, size, kind, download_url}— read it with Get Approval Card File.run_id(integer, optional, nullable) — the AI run that produced the card; must be a run on this ticketdiff_from_id(integer, optional, nullable) — an earlier card on the same ticket this one revises
Returns 201 with the new card (fields as in List Approval Cards).
Authentication: Bearer token with the support:write scope, or an authenticated session, from a caller with an active support role that can work tickets (the viewer role is read-only). No tenant context — this endpoint is central.
If this ticket was merged into another ticket, the request is refused with a 422 whose merged_into (id, number) names the live ticket — send the action there instead.
Request
Responses
- 201
- 401
- 403
- 404
- 409
- 422
- 429
Created
Response Headers
Unauthorized
Response Headers
Forbidden
Response Headers
Not Found
Response Headers
Conflict
Response Headers
Unprocessable Content
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.