Documentation menu
These are pre-release docs from the main branch.

API reference

23 operations in tickets. Raw spec: openapi.json.

GET/api/v1/tickets

GET /api/tickets — filtered, priority-ordered list; each row carries its custom-field values (the private build tracker 2.17) fetched in one batched query. Pagination is envelope-compatible: the body stays the plain ticket array; deep-page walks pass the opaque `cursor` query param (from the previous page's `x-next-cursor` response header) and the server resumes strictly after that row — keyset, not O(offset) scan (docs/perf.md F2).

Parameters

NameInTypeRequiredDescription
board_idquery—No
assignee_idquery—No
client_idquery—No
categoryquerynull | TicketStatusCategoryNo
priorityquerynull | PriorityNo
tagsquery—NoComma-separated tag ids (Wave 11 lane 9): AND semantics — tickets must carry every listed tag.
limitqueryintegerNo
offsetqueryintegerNo
cursorquery—NoKeyset cursor (Wave 15 lane 3): the opaque `x-next-cursor` value from the previous page's last row. Resumes the ranked list strictly after that row — deep pages stop paying O(offset) scan cost (docs/perf.md F2). When present, `offset` is ignored.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tickets

POST /api/tickets — allocates the human ticket number, initializes SLA clocks, validates + stores any initial custom-field values (REQUIRED definitions enforced), and emits `ticket.created` — all in one transaction. Instant-first (IF1/IF2): routed through the shared idempotency gate and returns the read-your-write `mutation` metadata.

Request body

CreateTicketRequest

Responses

StatusDescription
200
409version conflict or idempotency key reused
422missing required custom fields or invalid value

Security: bearer

GET/api/v1/tickets/my-work

GET /api/tickets/my-work — everything assigned to me, not closed, across all boards, priority-then-SLA-due ordered (the unified cross-board view from §1; the private build tracker 2.11 adds client/board annotations).

Responses

StatusDescription
200

Security: bearer

GET/api/v1/tickets/{id}

GET /api/tickets/{id} — a conditional read.

The response carries the entity's weak validator (`ETag`) built from its monotonic write seq, so a caller that still holds that revision sends `If-None-Match` and gets a bodyless `304 Not Modified` instead of the full representation. Entities without a counter row behave exactly as before: no validator, no conditional short-circuit.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
304not modified; the If-None-Match validator matched the entity's current revision

Security: bearer

PATCH/api/v1/tickets/{id}

PATCH /api/tickets/{id} — partial update; validates + upserts any provided custom-field values; emits `ticket.updated`, `ticket.field_set` (per changed field), and `ticket.assigned` when the assignee changes. Instant-first (IF1/IF2): idempotency-keyed, version- gated, and returns prior + new values for undo.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

PatchTicketRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)
409version conflict or idempotency key reused
422invalid custom-field value

Security: bearer

GET/api/v1/tickets/{id}/checklist

GET /api/tickets/{id}/checklist — lazily instantiates any template items the ticket has not picked up yet, then returns the full view.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tickets/{id}/checklist

POST /api/tickets/{id}/checklist — appends an ad-hoc checklist item. Ad-hoc items never block closure; emits `ticket.checklist_updated`. Instant-first (IF1/IF2): idempotency-keyed and returns the read-your-write mutation metadata for the created item.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

AddChecklistItemRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)
409idempotency key reused

Security: bearer

POST/api/v1/tickets/{id}/checklist/complete-all

POST /api/tickets/{id}/checklist/complete-all — the close gate's Option A batch (docs/notes-tasks-workbar.md §4.2/§5.3): every listed task flips to `checked` AND lands its server-composed auto-append note, ALL in one transaction with per-item idempotency keys (`complete-all:{ticket}:{item}`- derived under the caller's envelope key). Items already terminal (checked/N-A) are skipped idempotently; an unknown id 404s the whole batch (atomicity). Every appended note carries `workflow_meta { kind: "task_closeall", auto: true, item_ids: [..] }` — the REPORTED auto-close provenance the close-gate dialog displays. Also usable pre-close, not only at the gate.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

CompleteAllRequest

Responses

StatusDescription
200
404ticket or a listed item not visible in scope (rls_sparse; whole batch atomic)
409idempotency key reused

Security: bearer

POST/api/v1/tickets/{id}/checklist/{item_id}

POST /api/tickets/{id}/checklist/{item_id} — toggles one checklist item; emits `ticket.checklist_updated` in the same transaction. Instant-first (IF1/IF2): idempotency-keyed and returns the read-your-write mutation metadata for the toggled item.

Parameters

NameInTypeRequiredDescription
idpathstringYes
item_idpathstringYes

Request body

SetChecklistItemRequest

Responses

StatusDescription
200
404ticket or item not visible in scope (rls_sparse)
409idempotency key reused

Security: bearer

GET/api/v1/tickets/{id}/comments

GET /api/tickets/{id}/comments — internal notes require ticket view; the portal surface filters kinds separately (portal v1).

Parameters

NameInTypeRequiredDescription
idpathstringYes
kindquerynull | NoteKindNoOptional taxonomy filter (`internal`, `client_visible`, `in_house_update`). Omit for all kinds (staff view).

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tickets/{id}/comments

POST /api/tickets/{id}/comments — adds a structured note; @mentions fan out into the notification center. Instant-first (IF1/IF2): idempotency-keyed and returns the read-your-write mutation metadata.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

AddCommentRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)
409idempotency key reused

Security: bearer

GET/api/v1/tickets/{id}/decision-suggestions

GET /api/tickets/{id}/decision-suggestions — the workbench card's read. Optimistic UI rides the instant-first envelope upstream; this endpoint is a plain RLS'd read scoped by the ticket gate: the `tickets:view` permission, then the board gate on the ticket's own board (403), then the ticket itself (404 outside the org).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
403missing tickets:view, or a board denial
404ticket not visible in scope (rls_sparse)

Security: bearer

GET/api/v1/tickets/{id}/emails

GET /api/tickets/{id}/emails — every ledger row routed to this ticket (inbound + outbound), newest first, with reply-chain parents resolved and outbound delivery state projected from the retry ledger.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404ticket not visible in scope

Security: bearer

GET/api/v1/tickets/{id}/note-draft

GET /api/tickets/{id}/note-draft — restores the caller's autosaved composer draft (IF2, §5.2). Typed absence (200 with `draft: null`), never a 404: an absent draft is a normal first-open state, not a miss.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200

Security: bearer

PUT/api/v1/tickets/{id}/note-draft

PUT /api/tickets/{id}/note-draft — debounced autosave of the workbar composer draft (NOT a comment; never renders on any ledger). Fresh drafts ride the IF idempotency-keyed write path and opportunistically purge 30-day-stale rows.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

PutNoteDraftRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)

Security: bearer

DELETE/api/v1/tickets/{id}/note-draft

DELETE /api/tickets/{id}/note-draft — explicit draft discard.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200draft cleared (idempotent; 200 even with none)

Security: bearer

POST/api/v1/tickets/{id}/notes/refine

POST /api/tickets/{id}/notes/refine — Tier-2 grammar pass (docs/notes- tasks-workbar.md §2.5): sends the draft to the org's configured CompletionProvider (BYOK seam) and returns a PROPOSAL. NEVER a direct write: the ledger is untouched; the composer shows a diff with Apply/Discard and Apply rides the normal submit path. Provider unconfigured/unavailable → typed `status: "unavailable"` + the reason — the Tier-1 Tidy always works and the UI owns the "not available yet" state (Template-button pattern).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

RefineNoteRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)

Security: bearer

POST/api/v1/tickets/{id}/revert

POST /api/tickets/{id}/revert — undo one server-accepted edit by restoring its prior values as a NORMAL authorized idempotent write (IF1 envelope end to end). Appends a new trail row (op `revert`), emits `ticket.reverted` on the event bus, and writes the audited `ticket.reverted` trail row — the append-only history is never rewritten.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

RevertTicketRequest

Responses

StatusDescription
200
409version conflict, idempotency key reuse, or undo_reverts_only (closed record, whole-record revert)
422unknown target seq, non-restorable step/field, or nothing to restore

Security: bearer

GET/api/v1/tickets/{id}/similar-tickets

GET /api/tickets/{id}/similar-tickets — retrieval top-k + per-candidate relatedness Noul. Degrade-never-5xx: a quiet decision edge returns the plain retrieval list, never an error.

Parameters

NameInTypeRequiredDescription
idpathstringYes
qquery—NoOverride the retrieval text (defaults to the ticket's own title plus a body excerpt). Clamped like the search surface.

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)

Security: bearer

GET/api/v1/tickets/{id}/sla

GET /api/tickets/{id}/comments — optional kind filter; internal notes require ticket view. The portal surface must consume the dedicated client-visible read (`core-db::tickets::list_client_visible_comments`), never this staff endpoint. GET /api/tickets/{id}/sla — clock state plus a live business-time remaining estimate for the resolution budget (drives the UI badge).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200

Security: bearer

POST/api/v1/tickets/{id}/status

POST /api/tickets/{id}/status — the guarded lifecycle transition: domain transition rules, pre-close checklist enforcement (typed 409 listing the incomplete required items when the board enforces), SLA pause bookkeeping, and the `ticket.status_changed` event, all in one tx. Instant-first (IF1/IF2): idempotency-keyed, version-gated, read-your-write.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ChangeStatusRequest

Responses

StatusDescription
200
404ticket not visible in scope (rls_sparse)
409pre-close checklist blocked, version conflict, or idempotency key reused
422invalid transition

Security: bearer

GET/api/v1/tickets/{id}/time-entries

GET /api/tickets/{id}/time-entries

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200

Security: bearer

GET/api/v1/tickets/{id}/undo-stack

GET /api/tickets/{id}/undo-stack — the per-field / per-record applied-state history read model for one record.

Parameters

NameInTypeRequiredDescription
idpathstringYes
limitquery—NoMaximum steps / field rows returned (1–200, default 50). Newest first; the first step's `seq` is the record's current version.

Responses

StatusDescription
200
404ticket not visible in scope

Security: bearer