> For the complete documentation index, see [llms.txt](https://hub.equipme.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hub.equipme.io/development/api-reference-app/ticket.md).

# Ticket

A **thread** is the conversational container: it has a status (`OPEN` / `CLOSED`), an optional channel binding, messages and participants, and it can be the parent of sub-threads.

A **ticket** is the business-layer overlay on a thread. There is always exactly one thread per ticket, and the ticket carries the assignee, agent, contact, opener, type, status and labels. Not every thread has a ticket — free threads exist without one.

## List tickets

> Returns the tickets the caller can read, ordered by \`id\` unless \`sort\` says otherwise. \`count\` is the\
> total number of matching tickets, independent of \`offset\` and \`limit\`.\
> \
> The grammar of \`filter\`, \`search\`, \`sort\` and \`fields\` is described in the introduction; what a ticket\
> accepts for each of them is this:\
> \
> \*\*Filterable\*\* — \`id\`, \`threadId\`, \`assigneeId\`, \`agentId\`, \`contactId\`, \`openerId\`, \`subject\`,\
> \`summary\`, \`createdAt\`, \`updatedAt\`, and paths into the taxonomy and the labels, such as\
> \`status.name\`, \`type.name\`, \`impact.level\`, \`priority.level\`, \`urgency.level\` or \`labels.name\`.\
> \
> \*\*Sortable\*\* — \`id\`, \`subject\`, \`summary\`, \`createdAt\`, \`updatedAt\`, and \`relation.field\` for\
> \`assignee\`, \`agent\`, \`contact\`, \`status\`, \`type\`, \`impact\`, \`urgency\` and \`priority\`, for example\
> \`sort=priority.level:desc,createdAt\`.\
> \
> \*\*Searchable\*\* — \`subject\` and \`summary\` of the ticket, plus the email address, first name and last\
> name of the contact it was raised for. So \`search=lovelace\` finds the tickets of that person without\
> knowing their id.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread** is the conversational container: it has a status (`OPEN` / `CLOSED`), an optional channel\nbinding, messages and participants, and it can be the parent of sub-threads.\n\nA **ticket** is the business-layer overlay on a thread. There is always exactly one thread per ticket, and\nthe ticket carries the assignee, agent, contact, opener, type, status and labels. Not every thread has a\nticket — free threads exist without one.\n","name":"ticket"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.ListTicketsResponse-domain_Ticket":{"properties":{"count":{"type":"integer"},"data":{"items":{"$ref":"#/components/schemas/domain.Ticket"},"type":"array","uniqueItems":false}},"type":"object"},"domain.Ticket":{"properties":{"agent":{"$ref":"#/components/schemas/domain.Contact"},"agentId":{"type":"integer"},"assignee":{"$ref":"#/components/schemas/domain.Contact"},"assigneeId":{"type":"integer"},"contact":{"$ref":"#/components/schemas/domain.Contact"},"contactId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"integer"},"impact":{"$ref":"#/components/schemas/domain.TicketImpact"},"impactName":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/domain.Label"},"type":"array","uniqueItems":false},"opener":{"$ref":"#/components/schemas/domain.Contact"},"openerId":{"type":"integer"},"priority":{"$ref":"#/components/schemas/domain.TicketPriority"},"priorityName":{"type":"string"},"status":{"$ref":"#/components/schemas/domain.TicketStatus"},"statusName":{"type":"string"},"subject":{"type":"string"},"summary":{"type":"string"},"thread":{"$ref":"#/components/schemas/domain.Thread"},"threadId":{"type":"integer"},"type":{"$ref":"#/components/schemas/domain.TicketType"},"typeName":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"urgency":{"$ref":"#/components/schemas/domain.TicketUrgency"},"urgencyName":{"type":"string"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.TicketImpact":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Label":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI label classification: a label is a candidate only\nwhen AutoAssign is on, and then AssignmentContext must be non-empty. Description stays\nhuman-facing — the model reads AssignmentContext.","type":"boolean"},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketPriority":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketStatus":{"properties":{"allowedSourceStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"allowedTargetStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"isDefault":{"type":"boolean"},"name":{"type":"string"},"position":{"type":"integer"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"parent":{"$ref":"#/components/schemas/domain.Thread"},"parentId":{"type":"integer"},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"type":"string"},"domain.TicketType":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI ticket-type classification, mirroring DisplayName.\nRead-only today: ticket types are seeded and have no create/patch surface yet, so both\nstay at their defaults until that surface lands.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketUrgency":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/tickets":{"get":{"description":"Returns the tickets the caller can read, ordered by `id` unless `sort` says otherwise. `count` is the\ntotal number of matching tickets, independent of `offset` and `limit`.\n\nThe grammar of `filter`, `search`, `sort` and `fields` is described in the introduction; what a ticket\naccepts for each of them is this:\n\n**Filterable** — `id`, `threadId`, `assigneeId`, `agentId`, `contactId`, `openerId`, `subject`,\n`summary`, `createdAt`, `updatedAt`, and paths into the taxonomy and the labels, such as\n`status.name`, `type.name`, `impact.level`, `priority.level`, `urgency.level` or `labels.name`.\n\n**Sortable** — `id`, `subject`, `summary`, `createdAt`, `updatedAt`, and `relation.field` for\n`assignee`, `agent`, `contact`, `status`, `type`, `impact`, `urgency` and `priority`, for example\n`sort=priority.level:desc,createdAt`.\n\n**Searchable** — `subject` and `summary` of the ticket, plus the email address, first name and last\nname of the contact it was raised for. So `search=lovelace` finds the tickets of that person without\nknowing their id.\n","operationId":"listTickets","parameters":[{"description":"JSON filter expression","in":"query","name":"filter","schema":{"type":"string"}},{"description":"Free-text term across subject, summary and the contact","in":"query","name":"search","schema":{"type":"string"}},{"description":"Comma-separated field:direction terms","in":"query","name":"sort","schema":{"type":"string"}},{"description":"Selection of fields and relations","in":"query","name":"fields","schema":{"type":"string"}},{"description":"Number of tickets to skip","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"Page size, 1 to 100","in":"query","name":"limit","schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.ListTicketsResponse-domain_Ticket"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List tickets","tags":["ticket"]}}}}
```

## Open a ticket

> Opens a ticket. A ticket always lives on a thread: pass \`threadId\` to open it on an existing\
> conversation, or leave it out and a thread is created for it.\
> \
> \`description\` is required and becomes the ticket's opening message. \`subject\` is optional — when it\
> is left out, a subject is generated from the description.\
> \
> The status is taken from the tenant's default status; impact, urgency and priority are not accepted\
> here and stay unset until they are assigned. \`typeName\` refers to a ticket type by its name and may\
> be left out.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread** is the conversational container: it has a status (`OPEN` / `CLOSED`), an optional channel\nbinding, messages and participants, and it can be the parent of sub-threads.\n\nA **ticket** is the business-layer overlay on a thread. There is always exactly one thread per ticket, and\nthe ticket carries the assignee, agent, contact, opener, type, status and labels. Not every thread has a\nticket — free threads exist without one.\n","name":"ticket"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.AddTicketRequest":{"properties":{"agentId":{"type":"integer"},"assigneeId":{"type":"integer"},"contactId":{"type":"integer"},"description":{"type":"string"},"subject":{"type":"string"},"threadId":{"type":"integer"},"typeName":{"type":"string"}},"required":["description"],"type":"object"},"v1.AddTicketResponse":{"properties":{"data":{"$ref":"#/components/schemas/domain.Ticket"}},"type":"object"},"domain.Ticket":{"properties":{"agent":{"$ref":"#/components/schemas/domain.Contact"},"agentId":{"type":"integer"},"assignee":{"$ref":"#/components/schemas/domain.Contact"},"assigneeId":{"type":"integer"},"contact":{"$ref":"#/components/schemas/domain.Contact"},"contactId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"integer"},"impact":{"$ref":"#/components/schemas/domain.TicketImpact"},"impactName":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/domain.Label"},"type":"array","uniqueItems":false},"opener":{"$ref":"#/components/schemas/domain.Contact"},"openerId":{"type":"integer"},"priority":{"$ref":"#/components/schemas/domain.TicketPriority"},"priorityName":{"type":"string"},"status":{"$ref":"#/components/schemas/domain.TicketStatus"},"statusName":{"type":"string"},"subject":{"type":"string"},"summary":{"type":"string"},"thread":{"$ref":"#/components/schemas/domain.Thread"},"threadId":{"type":"integer"},"type":{"$ref":"#/components/schemas/domain.TicketType"},"typeName":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"urgency":{"$ref":"#/components/schemas/domain.TicketUrgency"},"urgencyName":{"type":"string"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.TicketImpact":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Label":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI label classification: a label is a candidate only\nwhen AutoAssign is on, and then AssignmentContext must be non-empty. Description stays\nhuman-facing — the model reads AssignmentContext.","type":"boolean"},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketPriority":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketStatus":{"properties":{"allowedSourceStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"allowedTargetStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"isDefault":{"type":"boolean"},"name":{"type":"string"},"position":{"type":"integer"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"parent":{"$ref":"#/components/schemas/domain.Thread"},"parentId":{"type":"integer"},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"type":"string"},"domain.TicketType":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI ticket-type classification, mirroring DisplayName.\nRead-only today: ticket types are seeded and have no create/patch surface yet, so both\nstay at their defaults until that surface lands.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketUrgency":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/tickets":{"post":{"description":"Opens a ticket. A ticket always lives on a thread: pass `threadId` to open it on an existing\nconversation, or leave it out and a thread is created for it.\n\n`description` is required and becomes the ticket's opening message. `subject` is optional — when it\nis left out, a subject is generated from the description.\n\nThe status is taken from the tenant's default status; impact, urgency and priority are not accepted\nhere and stay unset until they are assigned. `typeName` refers to a ticket type by its name and may\nbe left out.\n","operationId":"addTicket","requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"type":"object"},{"$ref":"#/components/schemas/v1.AddTicketRequest","summary":"request","description":"Ticket to open"}]}}},"description":"Ticket to open","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.AddTicketResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Open a ticket","tags":["ticket"]}}}}
```

## Get a ticket

> Returns a single ticket by its id.\
> \
> Two conditions have to hold, and they fail differently:\
> \
> \- The ticket must be within the caller's scope. A ticket that is not — because it belongs to another\
> &#x20; tenant, or the caller has no path to it at all — is answered with \`404\`, the same as an id that does\
> &#x20; not exist.\
> \- The caller must be allowed to read it, either through the ticket's access graph (being a participant\
> &#x20; of its thread, or holding an identity grant for the assigned contact) or through an explicit\
> &#x20; permission. A ticket the caller can see but must not read is answered with \`403\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread** is the conversational container: it has a status (`OPEN` / `CLOSED`), an optional channel\nbinding, messages and participants, and it can be the parent of sub-threads.\n\nA **ticket** is the business-layer overlay on a thread. There is always exactly one thread per ticket, and\nthe ticket carries the assignee, agent, contact, opener, type, status and labels. Not every thread has a\nticket — free threads exist without one.\n","name":"ticket"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.GetTicketResponse-domain_Ticket":{"properties":{"data":{"$ref":"#/components/schemas/domain.Ticket"}},"type":"object"},"domain.Ticket":{"properties":{"agent":{"$ref":"#/components/schemas/domain.Contact"},"agentId":{"type":"integer"},"assignee":{"$ref":"#/components/schemas/domain.Contact"},"assigneeId":{"type":"integer"},"contact":{"$ref":"#/components/schemas/domain.Contact"},"contactId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"integer"},"impact":{"$ref":"#/components/schemas/domain.TicketImpact"},"impactName":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/domain.Label"},"type":"array","uniqueItems":false},"opener":{"$ref":"#/components/schemas/domain.Contact"},"openerId":{"type":"integer"},"priority":{"$ref":"#/components/schemas/domain.TicketPriority"},"priorityName":{"type":"string"},"status":{"$ref":"#/components/schemas/domain.TicketStatus"},"statusName":{"type":"string"},"subject":{"type":"string"},"summary":{"type":"string"},"thread":{"$ref":"#/components/schemas/domain.Thread"},"threadId":{"type":"integer"},"type":{"$ref":"#/components/schemas/domain.TicketType"},"typeName":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"urgency":{"$ref":"#/components/schemas/domain.TicketUrgency"},"urgencyName":{"type":"string"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.TicketImpact":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Label":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI label classification: a label is a candidate only\nwhen AutoAssign is on, and then AssignmentContext must be non-empty. Description stays\nhuman-facing — the model reads AssignmentContext.","type":"boolean"},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketPriority":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketStatus":{"properties":{"allowedSourceStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"allowedTargetStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"isDefault":{"type":"boolean"},"name":{"type":"string"},"position":{"type":"integer"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"parent":{"$ref":"#/components/schemas/domain.Thread"},"parentId":{"type":"integer"},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"type":"string"},"domain.TicketType":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI ticket-type classification, mirroring DisplayName.\nRead-only today: ticket types are seeded and have no create/patch surface yet, so both\nstay at their defaults until that surface lands.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketUrgency":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/tickets/{ticketId}":{"get":{"description":"Returns a single ticket by its id.\n\nTwo conditions have to hold, and they fail differently:\n\n- The ticket must be within the caller's scope. A ticket that is not — because it belongs to another\n  tenant, or the caller has no path to it at all — is answered with `404`, the same as an id that does\n  not exist.\n- The caller must be allowed to read it, either through the ticket's access graph (being a participant\n  of its thread, or holding an identity grant for the assigned contact) or through an explicit\n  permission. A ticket the caller can see but must not read is answered with `403`.\n","operationId":"getTicket","parameters":[{"description":"Ticket id","in":"path","name":"ticketId","required":true,"schema":{"type":"integer"}},{"description":"Selection of fields and relations","in":"query","name":"fields","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.GetTicketResponse-domain_Ticket"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Get a ticket","tags":["ticket"]}}}}
```

## Change a ticket

> Changes a ticket. The body follows JSON Merge Patch semantics, so every field has three states and\
> they mean different things:\
> \
> \| The field is | Meaning |\
> \| --- | --- |\
> \| absent | leave it as it is |\
> \| \`null\` | clear it |\
> \| a value | set it |\
> \
> The taxonomy fields — \`statusName\`, \`typeName\`, \`impactName\`, \`urgencyName\`, \`priorityName\` — refer\
> to their entry by name, the same name the ticket returns when it is read. A status change is checked\
> against the tenant's allowed transitions; a transition that is not permitted is answered with \`400\`.\
> \
> \`labels\` replaces the whole set: send the labels the ticket should carry afterwards, not the ones to\
> add. Names that do not exist yet are created.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread** is the conversational container: it has a status (`OPEN` / `CLOSED`), an optional channel\nbinding, messages and participants, and it can be the parent of sub-threads.\n\nA **ticket** is the business-layer overlay on a thread. There is always exactly one thread per ticket, and\nthe ticket carries the assignee, agent, contact, opener, type, status and labels. Not every thread has a\nticket — free threads exist without one.\n","name":"ticket"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.PatchTicketRequest":{"properties":{"agentId":{"type":"integer"},"assigneeId":{"type":"integer"},"contactId":{"type":"integer"},"impactName":{"type":"string"},"labels":{"items":{"type":"string"},"type":"array","uniqueItems":false},"priorityName":{"type":"string"},"statusName":{"type":"string"},"subject":{"type":"string"},"typeName":{"type":"string"},"urgencyName":{"type":"string"}},"type":"object"},"v1.PatchTicketResponse":{"properties":{"data":{"$ref":"#/components/schemas/domain.Ticket"}},"type":"object"},"domain.Ticket":{"properties":{"agent":{"$ref":"#/components/schemas/domain.Contact"},"agentId":{"type":"integer"},"assignee":{"$ref":"#/components/schemas/domain.Contact"},"assigneeId":{"type":"integer"},"contact":{"$ref":"#/components/schemas/domain.Contact"},"contactId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"integer"},"impact":{"$ref":"#/components/schemas/domain.TicketImpact"},"impactName":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/domain.Label"},"type":"array","uniqueItems":false},"opener":{"$ref":"#/components/schemas/domain.Contact"},"openerId":{"type":"integer"},"priority":{"$ref":"#/components/schemas/domain.TicketPriority"},"priorityName":{"type":"string"},"status":{"$ref":"#/components/schemas/domain.TicketStatus"},"statusName":{"type":"string"},"subject":{"type":"string"},"summary":{"type":"string"},"thread":{"$ref":"#/components/schemas/domain.Thread"},"threadId":{"type":"integer"},"type":{"$ref":"#/components/schemas/domain.TicketType"},"typeName":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"urgency":{"$ref":"#/components/schemas/domain.TicketUrgency"},"urgencyName":{"type":"string"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.TicketImpact":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Label":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI label classification: a label is a candidate only\nwhen AutoAssign is on, and then AssignmentContext must be non-empty. Description stays\nhuman-facing — the model reads AssignmentContext.","type":"boolean"},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketPriority":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketStatus":{"properties":{"allowedSourceStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"allowedTargetStatuses":{"items":{"$ref":"#/components/schemas/domain.TicketStatus"},"type":"array","uniqueItems":false},"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"isDefault":{"type":"boolean"},"name":{"type":"string"},"position":{"type":"integer"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"parent":{"$ref":"#/components/schemas/domain.Thread"},"parentId":{"type":"integer"},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"type":"string"},"domain.TicketType":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext gate AI ticket-type classification, mirroring DisplayName.\nRead-only today: ticket types are seeded and have no create/patch surface yet, so both\nstay at their defaults until that surface lands.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.TicketUrgency":{"properties":{"color":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"displayName":{"type":"string"},"level":{"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/tickets/{ticketId}":{"patch":{"description":"Changes a ticket. The body follows JSON Merge Patch semantics, so every field has three states and\nthey mean different things:\n\n| The field is | Meaning |\n| --- | --- |\n| absent | leave it as it is |\n| `null` | clear it |\n| a value | set it |\n\nThe taxonomy fields — `statusName`, `typeName`, `impactName`, `urgencyName`, `priorityName` — refer\nto their entry by name, the same name the ticket returns when it is read. A status change is checked\nagainst the tenant's allowed transitions; a transition that is not permitted is answered with `400`.\n\n`labels` replaces the whole set: send the labels the ticket should carry afterwards, not the ones to\nadd. Names that do not exist yet are created.\n","operationId":"patchTicket","parameters":[{"description":"Ticket id","in":"path","name":"ticketId","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"type":"object"},{"$ref":"#/components/schemas/v1.PatchTicketRequest","summary":"request","description":"Fields to change"}]}}},"description":"Fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.PatchTicketResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Conflict"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Change a ticket","tags":["ticket"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://hub.equipme.io/development/api-reference-app/ticket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
