| Location | Name | Required | Meaning |
|---|---|---|---|
| path | id | Yes | Use the ID from an authorized staff response; source validators reject malformed IDs. |
| header | X-Admin-CSRF | Yes | Session-bound token from the staff page; not a developer credential. |
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"open",
"pending",
"solved",
"spam"
]
},
"assignee": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 128
},
{
"type": "null"
}
]
},
"tags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 32
},
"maxItems": 20
}
},
"required": [],
"minProperties": 1,
"description": "At least one of status, assignee or tags is required. Each change is audited separately."
}{error:{code,message}} for CloudError failures, attaches Request-Id, and disables caching. Handle 401 by signing in; show role refusal for 403 forbidden, reauthenticate only for step_up_required, and collect a reason for 400 reason_required. A CSRF failure needs a current page/session token, not blind retries. Operation-specific 404/409/413/422/502/503 errors retain their status. Honor Retry-After on 429./** PATCH /api/admin/support/tickets/:id {status?, assignee?, tags?} (support.assign); each change audited on its own. */
export const PATCH = adminRoute("support.assign", async (request, ctx, { params }: Context) => {
const id = ticketIdParam((await params).id);
const body = await readBody(request, PATCH_BODY);
let row;
if (body.status !== undefined) row = await setStatus(ctx, id, body.status);
if (body.assignee !== undefined) row = await assign(ctx, id, body.assignee);
if (body.tags !== undefined) row = await setTags(ctx, id, body.tags);
return adminJson({ ticket: row });
});