Skip to main content
This walkthrough takes one ticket through its whole life using curl. You build a maintenance-request workflow with four states, attach a resolution-notes form to the closing step, and then run a real ticket from new to resolved. Every step also has a short Console alternative. See Ticketing in the Console for the full UI guide.

Before you start

Creating, assigning, and transitioning tickets needs only project membership.
1

Set environment variables

Every request sends these headers:Successful responses use the envelope {"ok": 1, "data": {...}}. Errors return {"ok": 0, "message": "…", "detail": "…"} with an HTTP 4xx or 5xx status.
Console: you don’t need tokens. Sign in, open your project, and go to Operations → Ticketing.
2

Create the resolution-notes form

A catalog form is a JSON Schema plus bindings that copy submitted fields into the ticket. This one requires a resolution string and writes it to metadata.resolution.
The workflow refers to this form by name. Posting the same name again, or patching the form, creates version 2. A require_form with no version always uses the latest version. See Forms for pinning.
Every field you bind must be present in the submitted payload, even if the schema marks it optional. Bind only fields you will always send.
Console: go to Ticketing → Forms, click New form, paste the YAML below, check Schema preview, then click Create form.
3

Validate the workflow

POST /ticket-workflows/validate checks a spec without saving it. Send the same {"workflow": {...}} body you will use to create it.
What this spec says:A valid spec returns:
Validation problems still return HTTP 200, with valid: false and one entry per problem. For example, a typo "to": "resolvd" in the third transition returns:
The graph checks require that every label is reachable from initial, every non-terminal label has an outgoing transition, and at least one path reaches a terminal label. See Workflows for the full rule list. A body that is not a parseable workflow (for example, a missing name or initial) returns HTTP 400 instead.
Console: go to Ticketing → Workflows, click New workflow, open the YAML tab, paste the spec (the object inside "workflow", written as YAML), and click Validate. A valid spec shows YAML OK. The Graph tab draws the states and transitions.
4

Create the workflow

Post the same body to /ticket-workflows. Capture two IDs from the response:
A new version starts inactive. Posting another spec with the same name adds version 2 under the same workflow_def_id.
Create saves the version even when the spec has validation errors; they appear in validation_report with "ok": false. Such a version cannot be activated. Validate first.
Console: click Save in the workflow editor. Saving creates a draft version; the status line shows Not active yet.
5

Activate the version

Tickets can only be created on a workflow that has an active version. Activate by version ID:
The response is the same version item with "is_active": true. Activating a later version moves new tickets to it; tickets already open stay on the version they started on.Optionally, list the states the version created:
Each item has id, label, system_class, color, is_initial, and is_terminal. new has "is_initial": true; resolved and cancelled have "is_terminal": true.
Console: click Activate and confirm the dialog. The workflow now appears on Board.
6

Create a ticket

Use workflow_def_id, not the version ID. Include a severity tag, because the workflow requires one on create.
Single-ticket responses return metadata as a base64-encoded JSON string. Decode it with jq -r '.data.item.metadata' | base64 -d, which prints {"location": "Packaging line 2"}. To get decoded metadata, list with fields: GET …/tickets?ids=$TICKET_ID&fields=id,title,metadata,state_label.
Ticket create and transition accept an Idempotency-Key header. Send one when you retry so a timed-out request does not create a second ticket.
Console: on Board, click New ticket, fill Title and Description, choose a value under Severity, and click Create. The Console’s Due date is stored as metadata.due_at, not the API due_at field, so it does not start due-policy timers.
7

Assign the ticket

The in_progress → resolved transition permits ticket.assignee, so assign the ticket to yourself. Send exactly one of assigned_user_id or assigned_group_id.
The ticket’s assigned_to is now your user ID. The user must be a member of the project. Assigning a user clears any group assignment, and the reverse. To undo, POST …/unassign with {"target": "user"}. You can also assign at create time with assigned_user_id or assigned_group_id. See Assignment & notifications.
Console: there is no assign control. Pick people under Assign users when you create the ticket instead. That adds user tags, and a ticket.assignee permit also accepts users tagged on the ticket, so the walkthrough still works.
8

Start work: new → in_progress

Transition by the target label key:
The call returns after the state change is saved, so a GET right afterwards shows "system_class": "in_progress". Any on_enter / on_exit actions on the transition run afterwards in the background (see Automation). This workflow has none.
Console: open the ticket and click Go next to in_progress under Transitions, or drag the card to the in_progress column on Board.
9

Resolve with the form: in_progress → resolved

This move requires the resolution-notes form. Send its fields in form_payload:
The payload is checked against the form schema, then the binding writes resolution into the ticket metadata. Nothing changes if any check fails:
Console: click Go next to resolved (or drag the card). The Transition form dialog (or Form required to move on the board) asks for resolution; fill it in and click Apply transition (or Move).
10

Check the history and final state

List the transition history. Rows are newest first:
Page with limit (default 10, max 200) and page. Each row stores the submitted form_payload as payload.Then fetch the ticket:
Console: the ticket moves to the resolved column, and Ticket detail shows Status terminal_success. The Console has no transition-history view; use the API for the audit trail.

Next steps