{"openapi":"3.1.0","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"/"}],"tags":[{"name":"tickets","description":"Support-facing ticket management"},{"name":"messages","description":"Per-ticket message listing"},{"name":"templates","description":"Message templates (canned replies) for the support UI"}],"paths":{"/api/support/templates/{id}":{"get":{"tags":["templates"],"summary":"Get message template by id","operationId":"getById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TEMPLATES_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageTemplateResponse"}}}}}},"put":{"tags":["templates"],"summary":"Update message template","description":"Multipart body: `json` part is an `UpdateMessageTemplateRequest` with the new title,\ncontent, optional categories and `existingAttachmentIds` — the subset of the\ntemplate's current attachments to keep. Attachments stored on the template but NOT\nlisted in `existingAttachmentIds` are deleted from nx-media best-effort after the DB\ncommit. New uploads arrive as `files` parts and get appended after the kept ones.\n","operationId":"update","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/UpdateTemplateMultipart"},"encoding":{"json":{"contentType":"application/json"},"files":{"contentType":"application/octet-stream"}}}}},"responses":{"403":{"description":"Missing TEMPLATES_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error. Possible `code` values: `TOO_MANY_ATTACHMENTS`,\n`ATTACHMENT_TOO_LARGE`, `ATTACHMENT_MIME_NOT_ALLOWED`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Title already used in this tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"nx-media rejected the upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageTemplateResponse"}}}}}},"delete":{"tags":["templates"],"summary":"Delete message template","operationId":"delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TEMPLATES_DELETE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Deleted"}}}},"/api/support/tickets/{ticketId}/messages":{"get":{"tags":["messages"],"summary":"List messages on a ticket","description":"Full chronological message list for the given ticket. Ordered by UUIDv7 id ascending\n(creation time). Tenant is taken from the `Tenant-Id` header. Not paginated — support\nthreads are short-lived, the full thread is returned in a single response.\n","operationId":"listByTicket","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TICKETS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Ticket not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MessageResponse"}}}}}}},"post":{"tags":["messages"],"summary":"Post a support reply","description":"Multipart body: `json` part is a `SupportReplyRequest` with the HTML body;\noptional `files` parts carry attachments. Attachments are uploaded to nx-media first,\nthen the message is persisted and synchronously forwarded to the ticket's source\nsystem (L2CMS, Telegram, …). If the source rejects the payload the local persist is\nrolled back and uploaded files are best-effort deleted — the two states never drift.\n","operationId":"reply","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/SupportReplyMultipart"},"encoding":{"json":{"contentType":"application/json"},"files":{"contentType":"application/octet-stream"}}}}},"responses":{"403":{"description":"Missing TICKETS_REPLY permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation failed. Possible `code` values: `EMPTY_REPLY`,\n`TOO_MANY_ATTACHMENTS`, `ATTACHMENT_TOO_LARGE`,\n`ATTACHMENT_MIME_NOT_ALLOWED`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Ticket not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Ticket is CLOSED (`code=TICKET_CLOSED`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"nx-media or source system rejected the call","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"201":{"description":"Created + delivered","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}}}},"/api/support/templates":{"get":{"tags":["templates"],"summary":"Search message templates","description":"Paginated list for the support UI. Tenant is taken from `Tenant-Id`. `search`\nmatches title case-insensitively. `category` is repeatable and narrows by ticket\ncategories (template matches any — OR semantics; unscoped templates with no\ncategories are always included as catch-all). Sort whitelist: `usageCount`\n(default, desc), `title`, `updatedAt`; anything else → 400. Content is returned\nas canonical HTML — the support UI pastes it verbatim into a reply.\n","operationId":"search","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]},"uniqueItems":true}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Pageable"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TEMPLATES_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Unsupported sort property","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PagedModelMessageTemplateResponse"}}}}}},"post":{"tags":["templates"],"summary":"Create message template","description":"Multipart body: `json` part is a `CreateMessageTemplateRequest` with the title,\ncontent and optional categories; optional `files` parts carry attachments. Attachments\nare uploaded to nx-media under `tickets/templates/{id}`. If anything fails the\nuploads are best-effort deleted — local persist and nx-media state never drift.\nContent is normalized through the canonical pipeline (same allowlist as message\nbodies, 32 KB cap). Title is unique per tenant; duplicate → 409 with code\n`TEMPLATE_TITLE_TAKEN`.\n","operationId":"create","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateTemplateMultipart"},"encoding":{"json":{"contentType":"application/json"},"files":{"contentType":"application/octet-stream"}}}}},"responses":{"403":{"description":"Missing TEMPLATES_CREATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error. Possible `code` values: `TOO_MANY_ATTACHMENTS`,\n`ATTACHMENT_TOO_LARGE`, `ATTACHMENT_MIME_NOT_ALLOWED`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Title already used in this tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"nx-media rejected the upload","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageTemplateResponse"}}}}}}},"/api/support/templates/{id}/usages":{"post":{"tags":["templates"],"summary":"Record template usage","description":"Increments the template's `usage_count` by 1. Called by the support UI each time a\ntemplate is pasted into a reply so the default `usageCount desc` sort surfaces\npopular templates.\n","operationId":"recordUsage","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TEMPLATES_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Recorded"}}}},"/api/support/tickets/{id}/status":{"patch":{"tags":["tickets"],"summary":"Update ticket status","description":"Transition OPEN ↔ CLOSED. No-op when the ticket is already in the requested state.\n","operationId":"updateStatus","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStatusRequest"}}},"required":true},"responses":{"403":{"description":"Missing TICKETS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketResponse"}}}}}}},"/api/support/tickets/{id}/priority":{"patch":{"tags":["tickets"],"summary":"Update ticket priority","operationId":"updatePriority","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePriorityRequest"}}},"required":true},"responses":{"403":{"description":"Missing TICKETS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketResponse"}}}}}}},"/api/support/tickets/{id}/category":{"patch":{"tags":["tickets"],"summary":"Update ticket category","operationId":"updateCategory","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCategoryRequest"}}},"required":true},"responses":{"403":{"description":"Missing TICKETS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketResponse"}}}}}}},"/api/support/tickets":{"get":{"tags":["tickets"],"summary":"Search tickets","description":"Paginated ticket list for the support UI. Tenant is taken from the `Tenant-Id` header.\nOptional filters: source, status, priority, category. `search` matches display_name\nand ticket_number case-insensitively. Sort whitelist: `lastMessageId` (default, desc),\n`updatedAt`.\n","operationId":"search_1","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["TELEGRAM","L2CMS","DISCORD"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["OPEN","CLOSED"]}},{"name":"priority","in":"query","required":false,"schema":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"]}},{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Pageable"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TICKETS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Unsupported sort property or missing Tenant-Id","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PagedModelTicketResponse"}}}}}}},"/api/support/tickets/{id}":{"get":{"tags":["tickets"],"summary":"Get ticket by id","operationId":"getById_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TICKETS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketResponse"}}}}}}},"/api/support/tickets/unanswered-count":{"get":{"tags":["tickets"],"summary":"Count unanswered tickets","description":"Tickets that are open and whose last message was written by a player (support has not\nreplied yet). Drives the sidebar badge on the support UI. Response carries the overall\n`total` plus a per-`source` breakdown (every `TicketSource` enum value is always\npresent, zero-padded when there are no matches).\n","operationId":"unansweredCount","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — required for tenant-scoped endpoints","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — required for tenant-scoped endpoints","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"App-Slug","in":"header","description":"App slug (e.g. adm) for permission-scope matching","required":false,"schema":{"type":"string"},"example":"adm"}],"responses":{"403":{"description":"Missing TICKETS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"502":{"description":"Bad Gateway","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UnansweredCountResponse"}}}}}}}},"components":{"schemas":{"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"UpdateMessageTemplateRequest":{"type":"object","properties":{"title":{"type":"string","maxLength":256,"minLength":0},"content":{"type":"string","minLength":1},"categories":{"type":"array","items":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]},"uniqueItems":true},"existingAttachmentIds":{"type":"array","items":{"type":"string","format":"uuid"}}},"required":["content","title"]},"UpdateTemplateMultipart":{"type":"object","properties":{"json":{"$ref":"#/components/schemas/UpdateMessageTemplateRequest"},"files":{"type":"array","format":"binary","items":{"type":"string","format":"binary"}}}},"AttachmentResponse":{"type":"object","properties":{"fileId":{"type":"string","format":"uuid"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","format":"int64"},"fileName":{"type":"string"},"relativePath":{"type":"string"},"orderIndex":{"type":"integer","format":"int32"}}},"MessageTemplateResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":"string"},"categories":{"type":"array","items":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]},"uniqueItems":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentResponse"}},"usageCount":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SupportReplyMultipart":{"type":"object","properties":{"json":{"$ref":"#/components/schemas/SupportReplyRequest"},"files":{"type":"array","format":"binary","items":{"type":"string","format":"binary"}}}},"SupportReplyRequest":{"type":"object","properties":{"content":{"type":"string"}}},"MessageResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ticketId":{"type":"string","format":"uuid"},"authorType":{"type":"string","enum":["PLAYER","SUPPORT","SYSTEM"]},"authorId":{"type":"string"},"authorDisplayName":{"type":"string"},"contentCanonical":{"type":"string"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentResponse"}},"sourceMetadata":{"type":"object","additionalProperties":{}},"createdAt":{"type":"string","format":"date-time"}}},"CreateMessageTemplateRequest":{"type":"object","properties":{"title":{"type":"string","maxLength":256,"minLength":0},"content":{"type":"string","minLength":1},"categories":{"type":"array","items":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]},"uniqueItems":true}},"required":["content","title"]},"CreateTemplateMultipart":{"type":"object","properties":{"json":{"$ref":"#/components/schemas/CreateMessageTemplateRequest"},"files":{"type":"array","format":"binary","items":{"type":"string","format":"binary"}}}},"UpdateStatusRequest":{"type":"object","properties":{"status":{"type":"string","enum":["OPEN","CLOSED"]}},"required":["status"]},"LastMessageResponse":{"type":"object","properties":{"messageId":{"type":"string","format":"uuid"},"contentPreview":{"type":"string"},"authorType":{"type":"string","enum":["PLAYER","SUPPORT","SYSTEM"]},"authorId":{"type":"string"},"authorDisplayName":{"type":"string"},"attachmentsCount":{"type":"integer","format":"int32"},"createdAt":{"type":"string","format":"date-time"}}},"TicketResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"ticketNumber":{"type":"string"},"source":{"type":"string","enum":["TELEGRAM","L2CMS","DISCORD"]},"displayName":{"type":"string"},"languageCode":{"type":"string"},"category":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]},"priority":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"]},"status":{"type":"string","enum":["OPEN","CLOSED"]},"subject":{"type":"string"},"avatarMediaId":{"type":"string","format":"uuid"},"lastMessage":{"$ref":"#/components/schemas/LastMessageResponse"},"sourceMetadata":{"type":"object","additionalProperties":{}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UpdatePriorityRequest":{"type":"object","properties":{"priority":{"type":"string","enum":["LOW","NORMAL","HIGH","URGENT"]}},"required":["priority"]},"UpdateCategoryRequest":{"type":"object","properties":{"category":{"type":"string","enum":["TECHNICAL_SUPPORT","GAMEPLAY","DONATE","RULE_VIOLATIONS","RMT","OTHER"]}},"required":["category"]},"Pageable":{"type":"object","properties":{"page":{"type":"integer","format":"int32","minimum":0},"size":{"type":"integer","format":"int32","minimum":1},"sort":{"type":"array","items":{"type":"string"}}}},"PageMetadata":{"type":"object","properties":{"size":{"type":"integer","format":"int64"},"number":{"type":"integer","format":"int64"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int64"}}},"PagedModelTicketResponse":{"type":"object","properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/TicketResponse"}},"page":{"$ref":"#/components/schemas/PageMetadata"}}},"UnansweredCountResponse":{"type":"object","properties":{"source":{"type":"object","additionalProperties":{"type":"integer","format":"int64"}},"total":{"type":"integer","format":"int64"}}},"PagedModelMessageTemplateResponse":{"type":"object","properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/MessageTemplateResponse"}},"page":{"$ref":"#/components/schemas/PageMetadata"}}}}}}