Create 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 create ticket endpoint files a new ticket on behalf of a customer. Use it when an external system needs Tickiti to take ownership of a piece of work — for example a sales portal logging an enquiry, an order system creating a delivery exception ticket, or a CRM raising a contract review.

Endpoint

POST /api/v1/tickets

Headers

  1. Authorization: Bearer YOUR_TOKEN — required. Token must have the tickets:write ability.
  2. Idempotency-Key: <uuid> — required. See Idempotency.
  3. Content-Type: application/json — required.

Body

Required fields:

  1. originator_email_address — the email of the customer the ticket is filed for. The address becomes the ticket’s originator and its first participant; it does not need a Tickiti account.

You must provide exactly one of the following payload shapes:

  1. A) Subject + content: data.subject and data.content (HTML allowed). Use when the integration writes its own subject and body.
  2. B) Template + data: template_identifier plus a data object — render a Tickiti email template by identifier with bound data. Use when content should follow a configured template.
  3. C) Intervention + data: intervention (the intervention’s name), a data object and a uid — drive a Tickiti intervention with bound data, the most expressive option. uid is your own reference for the request and is required with this shape. See Interventions guide.

Optional fields:

  1. queue_name — the queue to place the ticket in, by name. Send it at the top level or as data.queue_name; if you send both they must name the same queue. Defaults to Inbox. Cannot be set for interventions (the intervention picks the queue), and a ticket cannot be created in the Drafts queue. Naming a queue also tells Tickiti the ticket belongs there — see Where the ticket lands. List the queue names with POST /api/v1/tickets/queues.
  2. data.priority — "10" Low, "20" Normal, "30" High or "40" Urgent. Send it as a JSON string — an integer is rejected with 422. Omit to leave Tickiti’s default. An intervention configured to create urgent tickets overrides this.
  3. data.status — open (default), on-hold or closed.
  4. data.on_hold_until — the date the hold ends (YYYY-MM-DD). Required when data.status is on-hold.
  5. data.assigned_to_email — assign the ticket as it is created: a staff member’s email, or a team’s key team:<id> (list the keys with POST /api/v1/tickets/teams). Defaults to Unassigned. An intervention that specifies an assignee overrides this.
  6. participants — an array of email addresses added as participants as the ticket is created.
  7. is_public — whether the opening response is public (default true). Set false to open the ticket with an internal staff note.
  8. use_passed_originator_as_responder — author the opening response as the originator rather than as the token’s owner. With a customer originator the opening response is then a customer response — see Where the ticket lands.
  9. strict_tags — boolean, default false. Refuse the request when an @tag or #hashtag in data.content does not resolve, instead of posting it as text. See @tags and #hashtags.
  10. customer_name, company (up to 255 characters each) and other_fields — details about the person submitting the ticket. other_fields is an array of { "label": ..., "value": ... } objects (label up to 255 characters, value up to 1000). When any of these is present Tickiti records them, with the originator’s email, as an internal note on the ticket so staff see who submitted it and how.
  11. attachments — an array of { "sha256": ..., "name": ..., "file_size": ... } objects describing files already uploaded with the attachment-upload endpoint (see Attaching files). They are attached to the opening response as regular (non-inline) attachments.

Example A — subject + content

curl -X POST https://support.sole-provider.example/api/v1/tickets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "originator_email_address": "[email protected]",
    "data": {
      "queue_name": "Returns",
      "subject": "Carbon plate cracked on first marathon",
      "content": "The Vapor Pro 3 plate snapped at mile 14 of Berlin Marathon. Order SP-2412-0489."
    }
  }'

Example B — template + data

Render the warranty_acknowledgement template with order/customer data:

{
  "originator_email_address": "[email protected]",
  "template_identifier": "warranty_acknowledgement",
  "data": {
    "queue_name": "Returns",
    "order_reference": "SP-2412-0489",
    "product": "Vapor Pro 3",
    "customer_name": "Marcus Whitfield"
  }
}

Example C — intervention

Run the warranty_claim intervention. The intervention defines its own queue, templates and follow-up steps:

{
  "originator_email_address": "[email protected]",
  "intervention": "warranty_claim",
  "uid": "crm-claim-58221",
  "data": {
    "order_reference": "SP-2412-0489",
    "product": "Vapor Pro 3"
  }
}

Interventions are the recommended pattern for any workflow that recurs — see Interventions quick start. A request whose uid matches one Tickiti has already received continues that earlier ticket rather than opening a new one: Tickiti moves it to the intervention’s queue, applies data.status (default open) and posts the intervention’s responses onto it.

Example D — assigned, on hold, with participants and a file

{
  "originator_email_address": "[email protected]",
  "queue_name": "Returns",
  "participants": ["[email protected]"],
  "attachments": [
    { "sha256": "cc9b8a68...608baf", "name": "photo.jpg", "file_size": 482113 }
  ],
  "data": {
    "subject": "Replacement plate awaiting courier",
    "content": "<p>Replacement booked. @priya please confirm stock.</p>",
    "priority": "30",
    "status": "on-hold",
    "on_hold_until": "2026-05-08",
    "assigned_to_email": "team:3"
  }
}

Inline images

In the subject + content shape, embed an image in data.content as a base64 data URI — an <img src="data:image/png;base64,…"> element placed where the image should appear. Tickiti decodes it, stores it as an inline attachment, and rewrites the body to reference it, so it renders within the ticket.

Attaching files

Upload each file first with POST /api/v1/tickets/attachment-upload (tickets:write), sending it as multipart/form-data in a field named file. Files up to 25 MB are accepted; a file type an administrator has hard-blocked is refused with 403 attachment_blocked. The response describes the stored file:

{
  "ok": true,
  "data": { "sha256": "cc9b8a68...608baf", "name": "photo.jpg", "file_size": 482113 }
}

Pass that object, unchanged, in the attachments array of the create (or reply) request. Files are stored by content, so uploading the same bytes twice is harmless.

Where the ticket lands

Tickiti picks the queue in this order: the intervention’s queue, then queue_name / data.queue_name, then Inbox.

When the opening response is written by a non-staff author — for example with use_passed_originator_as_responder and a customer originator — and the ticket has no staff participant, Tickiti treats it as incoming customer mail and routes it to the inbox queue (the queue incoming email is delivered to), adding that queue’s inbox managers as participants. A request that names a queue, or that sets is_public to false, keeps the ticket in the queue it chose.

The response always reports where the ticket actually is: queue is the queue it is in, queue_requested the queue the request resolved to, and when the two differ rerouted is true and reroute_reason says why.

@tags and #hashtags

In an opening response written by a staff author, @name tags the staff member whose email or name starts with name — they are added as a participant and notified — and #word adds a hashtag. A tag starts with a letter, and a trailing full stop is not part of it, so “check with @dave.” tags dave. An @ or # inside a code block (<pre>, <code> or a Quill code block) is never a tag, so pasted diffs and logs post as written.

  1. An @tag that matches no staff member, or more than one, is kept as plain text and listed in tag_warnings as { "reason": "no_matches" | "not_unique", "tag": ..., "emails": [...] } (emails lists the candidates for not_unique).
  2. A hashtag that closely resembles an existing one is added as written and listed in hashtag_warnings as { "reason": "possible_misspelling", "tag": ..., "matches": [...] }.
  3. With "strict_tags": true Tickiti checks data.content before creating anything and refuses the request with 422 validation_failed, naming the tags under errors.tag_errors and errors.hashtag_errors (each a JSON-encoded list of the objects above).

Successful response

{
  "ok": true,
  "data": {
    "ticket_id": 1,
    "ticket_number": 220009,
    "secure_id": "q8Z2...",
    "queue": "Returns",
    "queue_requested": "Returns",
    "rerouted": false,
    "reroute_reason": null,
    "status": "open",
    "assigned_to_email": "Unassigned",
    "priority": "20",
    "tag_warnings": [],
    "hashtag_warnings": [],
    "used_template": false,
    "used_intervention": false,
    "used_private_template": false,
    "used_public_template": false
  }
}
  1. ticket_id, ticket_number — the ticket’s internal id and its 6-digit number. Either one references the ticket in the other ticket endpoints.
  2. secure_id — the key in the originator’s personal link to the ticket. Treat it as a secret. It is null when an intervention request continues an existing ticket.
  3. queue, queue_requested, rerouted, reroute_reason — see Where the ticket lands.
  4. status, assigned_to_email, priority — the ticket’s values after creation, including anything an intervention applied.
  5. tag_warnings, hashtag_warnings — see @tags and #hashtags.
  6. used_* — which payload path was taken.

Store ticket_number to read the ticket or reply to it later. For recurring workflows, an intervention (payload shape C) with a uid ties the ticket to your own record: Tickiti carries the uid through the intervention’s epilog callbacks. See Interventions technical reference.

Error responses

Every error has "ok": false and an error code. Validation errors list the problems per field under errors; the others carry a message:

{
  "ok": false,
  "error": "validation_failed",
  "errors": {
    "queue_name": ["TicketQueue not found."]
  }
}
  1. 422 validation_failed — a missing or malformed field, none or more than one payload shape, an unknown queue or intervention, a queue named alongside an intervention, queue_name and data.queue_name disagreeing, on-hold without data.on_hold_until, an unknown team key, unresolved tags under strict_tags, or a missing Idempotency-Key.
  2. 422 create_refused — Tickiti declined to post the opening response; message says why. Nothing is created.
  3. 422 create_failed — the ticket could not be created; message says why.
  4. 404 not_found — a record the request depends on does not exist; message names it.
  5. 409 idempotency_key_conflict — the idempotency key was already used with a different payload. See Idempotency.
  6. 401 unauthenticated — bad or missing token.
  7. 403 forbidden — token does not have the tickets:write ability, or the user it acts as may not create a ticket in the target queue (message says so).
  8. 500 internal_error — an unexpected server error.

What happens server-side

  1. Tickiti validates the request — including strict_tags — before anything is created.
  2. It picks the queue: the intervention’s queue, otherwise queue_name / data.queue_name, otherwise Inbox.
  3. It creates the ticket, applying data.status, data.on_hold_until, data.priority and data.assigned_to_email where supplied — otherwise status Open, Tickiti’s default priority and Unassigned.
  4. It adds the originator and every address in participants as participants. An intervention then applies its own settings (urgent priority, assignee, extra participants).
  5. It writes the opening response (public, or an internal note per is_public) using the chosen subject/content/template/intervention, resolving @tags and #hashtags and applying the customer-mail routing rule described above.
  6. It extracts any base64 data-URI images from the body, stores them as inline attachments, and rewrites the body to reference them; it attaches any uploaded attachments.
  7. It records customer_name / company / other_fields as an internal note when supplied.
  8. It dispatches the appropriate notification email to the originator (unless suppressed by the template configuration).

To change a ticket after creating it, use the Reply to ticket API; to delete, merge or link tickets, see the Ticket operations API.