{"openapi":"3.1.0","info":{"title":"Fesk API","version":"v1","description":"REST API for Fesk ticket management system."},"servers":[{"url":"https://api.fesk.io/v1"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"paths":{"/tickets":{"post":{"summary":"Create a ticket","operationId":"createTicket","description":"Create a new support ticket.\n\nThe ticket's project is resolved from (in order): the API key's project scope, a `project_id` query parameter, or a `project_id` field in the request body. A project-scoped API key is recommended, in which case `project_id` can be omitted entirely. If none of the three supply a project, the request fails with `400 \"Project ID is required.\"`\n\nThe `description` is Markdown (CommonMark with tables, strikethrough and bare links). It is kept and returned as `description`; `description_html` is its rendering, sanitized, and is what the app shows. HTML in it is not interpreted, a line break stays a line break, and an image outside the project's attachments becomes a link.\n\nA field the request body does not define is ignored without an error. That includes `status`: a new ticket always starts in the project's default status; change it afterwards with `PATCH /tickets/{id}/status`.\n\n**Required Scopes:** tickets:write","tags":["Tickets"],"parameters":[{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Optional alternative to a project-scoped API key or a body `project_id`.","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ticket_type_id","title"],"additionalProperties":false,"description":"A field not listed here is refused with 422 naming it. A ticket starts in the project's default status: `status` is refused here, and set with `PATCH /tickets/{id}/status`.","properties":{"project_id":{"type":"integer","description":"Project ID. Optional, only used when the API key is not project-scoped and no `project_id` query parameter is given."},"project":{"type":"string","description":"Project slug, as an alternative to `project_id`."},"ticket_type_id":{"type":"integer","description":"Ticket type ID"},"title":{"type":"string","maxLength":200,"description":"Ticket subject"},"description":{"type":"string","maxLength":50000,"description":"Detailed description (markdown supported)"},"priority":{"type":"string","enum":["low","medium","high","critical"],"default":"medium","description":"Priority level"},"assigned_to":{"type":["integer","null"],"minimum":1,"description":"User ID to assign the ticket to. Anything but a user id is refused with 422."},"custom_fields":{"type":"object","description":"Additional custom field key-value pairs. Anything but an object is refused with 422."},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":25,"description":"Names of the tags to put on the ticket. A name the project does not have yet is created; an existing tag is matched ignoring case and keeps its spelling. The response carries the resulting tags."},"requester_email":{"type":"string","format":"email","maxLength":254,"description":"The customer the ticket is opened for, when that is not the key's owner. An address of a user of the project's account makes that user the requester; any other address is kept as `custom_fields.contact_email`, and emailed replies from it count as the customer's. An invalid address, or one of a system account, answers 422."},"requester_name":{"type":"string","maxLength":200,"description":"The customer's name, kept as `custom_fields.contact_name` when `requester_email` is not a user of the account. Ignored without `requester_email`."},"external_id":{"type":"string","minLength":1,"maxLength":255,"description":"Another system's id for the ticket. Unique among the project's tickets that are not deleted, compared exactly: when another ticket has it, nothing is created and the answer is `409` with `error_code` `external_id_taken` and that ticket in `error.ticket`, so a sync that runs twice finds the ticket it made the first time."}}}}}},"responses":{"201":{"description":"Ticket created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ticket":{"$ref":"#/components/schemas/Ticket"},"warnings":{"type":"array","items":{"type":"string"},"description":"Present only when the ticket was created but its `tags` could not be written. Send them again with `PUT /tickets/{id}`; repeating the create would add a second ticket."}},"required":["ticket"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]},"get":{"summary":"List tickets","operationId":"listTickets","description":"Retrieve a paginated list of tickets with optional filtering and sorting.\n\nWith `q` the answer is a search instead, best match first: `per_page` tickets (100 unless it says otherwise) of page `page`, with `count` and `has_more` (whether a further page has matches) and no total. `sort` is ignored then; the filters still apply. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** tickets:read","tags":["Tickets"],"parameters":[{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Required only when the API key is not scoped to a project; a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer","default":1,"minimum":1}},{"name":"per_page","in":"query","required":false,"description":"Items per page (max 100). With `q` the default is 100.","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"sort","in":"query","required":false,"description":"Sort field with +/- prefix (e.g., -created_at, +priority). Allowed: id, title, status, priority, created_at, updated_at, assigned_to. Any other field is refused with 422. Ignored with `q`.","schema":{"type":"string","default":"-created_at"}},{"name":"status","in":"query","required":false,"description":"Filter by status id or label, e.g. `143` or `In Progress` (see `GET /statuses`). Repeat the parameter for several statuses (`status=Open&status=Waiting`); a ticket in any of them passes. Unknown values are rejected with 422 listing the project's statuses.","schema":{"type":"string"}},{"name":"priority","in":"query","required":false,"description":"Filter by priority. Any other value is refused with 422.","schema":{"type":"string","enum":["low","medium","high","critical"]}},{"name":"assignee","in":"query","required":false,"description":"Filter by the numeric id of the assigned user (see `GET /users`). `none` keeps the unassigned tickets. Another value is refused with 422.","schema":{"type":"integer"}},{"name":"tag","in":"query","required":false,"description":"Filter by tag id or name, e.g. `12` or `Billing`. Name several by repeating the parameter or separating them with commas (`Billing,Urgent`); a ticket must carry every tag named. A tag the project does not have matches no ticket: the result is empty, not an error.","schema":{"type":"string"}},{"name":"status_category","in":"query","required":false,"description":"Filter by status category. Name several by repeating the parameter or separating them with commas; `initial,active,paused` is every ticket that is not closed. Another value is refused with 422.","schema":{"type":"string","enum":["initial","active","paused","terminal"]}},{"name":"ticket_type_id","in":"query","required":false,"description":"Filter by ticket type id (see `GET /ticket-types`).","schema":{"type":"integer","minimum":1}},{"name":"created_after","in":"query","required":false,"description":"Tickets created at or after this ISO 8601 date or time (a time without an offset is UTC).","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","required":false,"description":"Tickets created before this ISO 8601 date or time, the moment itself excluded.","schema":{"type":"string","format":"date-time"}},{"name":"updated_after","in":"query","required":false,"description":"Tickets changed at or after this ISO 8601 date or time: for catching up after missed webhook deliveries, with `sort=+updated_at`.","schema":{"type":"string","format":"date-time"}},{"name":"external_id","in":"query","required":false,"description":"The ticket with this `external_id`, compared exactly (case included).","schema":{"type":"string"}},{"name":"updated_before","in":"query","required":false,"description":"Tickets last changed before this ISO 8601 date or time, the moment itself excluded.","schema":{"type":"string","format":"date-time"}},{"name":"q","in":"query","required":false,"description":"Search query: full-text over title and description, or a ticket number (`DEV-12`, or `12` for the project's own). Best match first, paged by `page` and `per_page`; `has_more` says whether another page has matches.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of tickets with metadata","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tickets":{"type":"array","items":{"$ref":"#/components/schemas/Ticket"}},"pagination":{"description":"Without `q`: the page and the total. With `q`: page, per_page and has_more, no total.","oneOf":[{"$ref":"#/components/schemas/PaginationMeta"},{"type":"object","properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"has_more":{"type":"boolean"}},"required":["page","per_page","has_more"]}]},"count":{"type":"integer","description":"Number of tickets on this page, with `q` only"},"has_more":{"type":"boolean","description":"With `q` only: whether the next page has matches too"},"ignored_parameters":{"type":"array","items":{"type":"string"},"description":"Present only when the request carried query parameters this endpoint does not define: their names. They were ignored. From 2027-02-01 such a request is answered with 422 instead."}},"required":["tickets"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation failed: an unknown status, more tags named than a ticket can carry, or (from 2027-02-01) a query parameter the endpoint does not define","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/tickets/{id}":{"get":{"summary":"Get a ticket","operationId":"getTicket","description":"Retrieve a single ticket by ID.\n\n**Comments:** `include=comments` adds the ticket's comments as `comments` on the ticket, as the first page of `GET /tickets/{id}/comments` (oldest first, at most 1,000, `include_internal` honoured; the list pages past it), so reading a ticket's discussion takes one request. It needs `comments:read` (or `comments:write`) besides `tickets:read`; a key without it gets `403` naming the scope.\n\n**Required Scopes:** tickets:read","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"include","in":"query","required":false,"description":"`comments` adds the ticket's comments; any other value answers 422","schema":{"type":"string","enum":["comments"]}},{"name":"include_internal","in":"query","required":false,"description":"With `include=comments`: internal notes are included unless this is `false` or `0`. Any other value is refused with 422.","schema":{"type":"string","enum":["true","false","1","0"],"default":"true"}}],"responses":{"200":{"description":"Ticket details","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ticket":{"allOf":[{"$ref":"#/components/schemas/Ticket"},{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"},"description":"Present only with `include=comments`"}}}]}},"required":["ticket"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`include=comments` with a key that holds no comments scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"An `include` value other than `comments`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"put":{"summary":"Update a ticket","operationId":"updateTicket","description":"Update an existing ticket. Only provided fields are changed; omitted fields remain unchanged.\n\n**Optimistic Concurrency:** Include the `version` field (from your last GET) in the request body. If the ticket has been modified since that version, you receive `409 Conflict`. When `version` is omitted, the server does not perform client-side conflict detection, but a `409` may still occur in rare cases of truly simultaneous writes (the internal storage layer always guards against corrupt partial writes). For multi-user integrations, always include `version`.\n\n**Description:** A `description` that is sent is Markdown and replaces `description_html` with its rendering, also on a ticket whose description was written in the app's editor. An empty string clears both.\n\n**Required Scopes:** tickets:write","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"description":"A field not listed here is refused with 422 naming it.","properties":{"project_id":{"type":"integer","description":"Project ID, when the API key is not project-scoped."},"project":{"type":"string","description":"Project slug, as an alternative to `project_id`."},"title":{"type":"string","maxLength":200,"description":"New title (cannot be empty)"},"description":{"type":"string","maxLength":50000,"description":"New description"},"status":{"type":["string","integer"],"description":"New status. Name the status by its id or its label, e.g. `143` or `\"In Progress\"` (case and punctuation are ignored). See `GET /statuses`. Unknown values are rejected with 422 listing the project's statuses."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"New priority"},"assigned_to":{"type":["integer","null"],"minimum":0,"description":"User ID to assign. Send `0` to unassign. Omit or send `null` to leave unchanged. Anything else is refused with 422."},"custom_fields":{"type":"object","description":"Custom field key-value pairs (merged with existing). Anything but an object is refused with 422."},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":25,"description":"Names of the tags the ticket carries from now on: they replace its current tags, and `[]` removes them all. A name the project does not have yet is created. Omit to leave the tags unchanged. Tags are not part of `version`: a request that only sets `tags` leaves it as it is."},"external_id":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"Set or change the ticket's external id, or send `null` to clear it. Another ticket of the project having it answers `409` (`external_id_taken`, the ticket in `error.ticket`) and changes nothing. Setting it raises `version`."},"version":{"type":"integer","description":"Expected version for optimistic concurrency. When provided, the server rejects the update with 409 if the ticket has been modified since this version. When omitted, no client-side conflict detection (a 409 may still occur on rare simultaneous writes)."}}}}}},"responses":{"200":{"description":"Ticket updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ticket":{"$ref":"#/components/schemas/Ticket"}},"required":["ticket"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"409":{"description":"Version conflict, ticket was modified by another client. Re-fetch and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]},"delete":{"summary":"Delete a ticket","operationId":"deleteTicket","description":"Soft delete a ticket\n\n**Required Scopes:** tickets:delete","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}}],"responses":{"200":{"description":"Ticket deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Ticket deleted"}}}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:delete"]}},"/tickets/{id}/status":{"patch":{"summary":"Update ticket status","operationId":"updateTicketStatus","description":"Move a ticket to another status, named by id or label (see `GET /statuses`). Optional neighbour IDs position the ticket within the target lane; `resolution_type` may be supplied when moving to a terminal (closed) status.\n\n**Required Scopes:** tickets:write","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":["string","integer"],"description":"New status. Name the status by its id or its label, e.g. `143` or `\"In Progress\"` (case and punctuation are ignored). See `GET /statuses`. Unknown values are rejected with 422 listing the project's statuses."},"after_id":{"type":["integer","null"],"description":"Place the ticket immediately after this ticket within the target lane"},"before_id":{"type":["integer","null"],"description":"Place the ticket immediately before this ticket within the target lane"},"resolution_type":{"type":["string","null"],"enum":["resolved","wont_fix","duplicate",null],"description":"Resolution reason, only valid when moving to a terminal/closed status"}}}}}},"responses":{"200":{"description":"Status updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ticket":{"$ref":"#/components/schemas/Ticket"}},"required":["ticket"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed, the ticket cannot be changed (merged or deleted), or the project's transition rules forbid the move: then `error.error_code` is `TRANSITION_NOT_ALLOWED` and `error.allowed_statuses` lists the statuses (`id`, `label`) the ticket may move to","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/tickets/bulk-update":{"post":{"summary":"Bulk update tickets","operationId":"bulkUpdateTickets","description":"Update up to 100 tickets of the project at once. Every field but `ticket_ids` is optional, and each one that is sent applies to every ticket.\n\nThe answer is `200` once the request was valid, whatever happened to the single tickets: a ticket that does not exist, is deleted or in another project, or could not be updated is named in `failed_ids` and described in `failures`, the others are in `tickets`. The fields are written one after the other (status, then priority and assignee, then tags), so a failed ticket may already carry some of them: `failures[].applied` names them. A body field the endpoint does not take answers `422`.\n\n**Required Scopes:** tickets:write","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ticket_ids"],"properties":{"ticket_ids":{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":100,"description":"Array of ticket IDs to update (max 100)"},"status":{"type":["string","integer"],"description":"New status for all tickets, by id or label (see `GET /statuses`). Each ticket goes through the regular status change: transition rules, SLA timers, history."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"New priority for all tickets"},"assigned_to":{"type":["integer","null"],"description":"User ID to assign the tickets to; `0` unassigns them. Omit or send `null` to leave the assignees unchanged."},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":25,"description":"Names of the tags every ticket carries from now on, as `tags` on `PUT /tickets/{id}`: they replace each ticket's current tags, and a name the project does not have yet is created."}}}}}},"responses":{"200":{"description":"Bulk update completed with summary","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"success_count":{"type":"integer","description":"Number of successfully updated tickets"},"failed_count":{"type":"integer","description":"Number of failed updates"},"failed_ids":{"type":"array","items":{"type":"integer"},"description":"IDs of tickets that failed to update"},"failures":{"type":"array","description":"One entry per ticket in `failed_ids`: why it failed and what was written before.","items":{"type":"object","properties":{"id":{"type":"integer"},"error_code":{"type":"string","description":"As `error.error_code` of a single update: not_found, validation_failed, TRANSITION_NOT_ALLOWED, version_conflict, internal_error, ..."},"message":{"type":"string"},"applied":{"type":"array","items":{"type":"string","enum":["status","priority","assigned_to"]},"description":"Fields already written to the ticket before it failed; empty when nothing was"}},"required":["id","error_code","message","applied"]}},"tickets":{"type":"array","items":{"$ref":"#/components/schemas/Ticket"},"description":"Array of successfully updated ticket objects"}},"required":["success_count","failed_count","failed_ids","failures","tickets"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A ticket of `ticket_ids` is in another project. No ticket was changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/tickets/{id}/history":{"get":{"summary":"List a ticket's history","operationId":"listTicketHistory","description":"Who changed what on a ticket, and when, newest first: status, priority, assignee, title and type changes, comments and files added, merges, and the events of SLAs, emails, AI and workflows.\n\n`changes` lists the fields an event changed with their old and new values; a status is its id and label. A change of custom fields or the description shows as `custom_fields` without its values: read the ticket for them. `details` carries a comment's or a file's id, or the tickets merged in. Any other data an event holds internally is left out. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** tickets:read","tags":["Tickets"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed)","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Events per page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"The ticket's events, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"ticket_id":{"type":"integer"},"event_type":{"type":"string","enum":["created","updated","status_changed","assigned","commented","merged","escalated","sla_paused","sla_resumed","sla_breached","ingested","transformed","ai_processed","delivered","email_sent","email_received","attachment_added","reopened","moved","deleted","workflow_stage_entered","workflow_step_completed","workflow_action_applied","workflow_transition_fired","workflow_hold_created","workflow_resolve_dispatched"]},"actor":{"type":"object","properties":{"type":{"type":"string","enum":["user","system","automation","ai"]},"id":{"type":["integer","null"],"description":"The user id for a user, else the system part that acted, if known"},"name":{"type":["string","null"],"description":"The user's name; null for every other actor"}},"required":["type","id","name"]},"changes":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","enum":["status","resolution_type","title","priority","assigned_to","ticket_type_id","custom_fields"]},"from":{"oneOf":[{"type":["string","integer","boolean","null"]},{"type":"object","properties":{"id":{"type":["integer","null"]},"label":{"type":"string"}},"description":"A status, by id and label"}]},"to":{"oneOf":[{"type":["string","integer","boolean","null"]},{"type":"object","properties":{"id":{"type":["integer","null"]},"label":{"type":"string"}},"description":"A status, by id and label"}]}},"required":["field","from","to"]}},"details":{"type":["object","null"],"additionalProperties":true,"description":"commented: comment_id, is_internal. attachment_added: attachment_id, filename, size_bytes. merged: merged_ticket_ids, merged_ticket_numbers."},"created_at":{"type":"string","format":"date-time"}},"required":["id","ticket_id","event_type","actor","changes","details","created_at"]}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"},"ignored_parameters":{"type":"array","items":{"type":"string"},"description":"Present only when the request carried query parameters this endpoint does not define: their names. They were ignored. From 2027-02-01 such a request is answered with 422 instead."}},"required":["events","pagination"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"A page or per_page out of range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/webhooks/{id}":{"patch":{"summary":"Change a webhook","operationId":"updateWebhook","description":"Change a webhook's `url`, `events` or `is_active`; a field left out stays as it is. The secret stays: rotate it with `POST /webhooks/{id}/rotate-secret`. A change takes effect for deliveries already queued, which go to the new URL. Subscribing to `comment.created` needs `comments:read` (or `comments:write`), as on create. The webhook follows the key that changed it from then on (see `api_key_id`). A field not listed is refused with 422.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","description":"A public HTTPS or HTTP URL"},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["ticket.created","ticket.updated","ticket.deleted","ticket.status_changed","ticket.assigned","ticket.merged","comment.created"]}},"is_active":{"type":"boolean","description":"false stops deliveries until it is true again"}}}}}},"responses":{"200":{"description":"The webhook as it is now, without its secret","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/Webhook"}},"required":["webhook"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope an event needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Not a webhook of the key's owner, or for a key pinned to a project not one of that project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"An unknown field or event, or a URL that is invalid or private","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]},"delete":{"summary":"Delete a webhook","operationId":"deleteWebhook","description":"Delete a webhook subscription. No further events will be delivered.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"Webhook deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Webhook deleted"}},"required":["message"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid webhook ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/webhooks/{id}/rotate-secret":{"post":{"summary":"Rotate a webhook's secret","operationId":"rotateWebhookSecret","description":"Give the webhook a new signing secret, returned once in this answer as at creation. Every delivery from now on is signed with it, retries of those already queued included; the old secret stops working at once.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"200":{"description":"The webhook with its new secret","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/WebhookCreated"}},"required":["webhook"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a webhook of the key's owner, or for a key pinned to a project not one of that project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/webhooks/{id}/deliveries":{"get":{"summary":"List a webhook's failed deliveries","operationId":"listWebhookDeliveries","description":"The deliveries Fesk gave up on, newest first: after their last attempt failed, or not sent because the URL was blocked or the account closed. Deliveries that arrived are not kept. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** tickets:read","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Failed deliveries","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The record's id, for redelivery"},"webhook_id":{"type":"integer"},"event":{"type":"string","enum":["ticket.created","ticket.updated","ticket.deleted","ticket.status_changed","ticket.assigned","ticket.merged","comment.created"]},"event_id":{"type":["string","null"],"description":"X-Webhook-Event-Id it was sent with; null for a failure recorded before ids were sent"},"delivery_id":{"type":["string","null"],"description":"X-Webhook-Delivery-Id it was sent with"},"status_code":{"type":["integer","null"],"description":"The receiver's status on the last attempt; null when it did not answer or nothing was sent"},"response":{"type":["string","null"],"description":"The receiver's answer, or why it was not sent, at most 2,000 bytes"},"attempts":{"type":"integer"},"failed_at":{"type":"string","format":"date-time"}},"required":["id","webhook_id","event","event_id","delivery_id","status_code","response","attempts","failed_at"]}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"},"ignored_parameters":{"type":"array","items":{"type":"string"},"description":"Present only when the request carried query parameters this endpoint does not define: their names. They were ignored. From 2027-02-01 such a request is answered with 422 instead."}},"required":["deliveries","pagination"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a webhook of the key's owner, or for a key pinned to a project not one of that project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"A page or per_page out of range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/webhooks/{id}/deliveries/{deliveryId}/redeliver":{"post":{"summary":"Send a failed delivery again","operationId":"redeliverWebhookDelivery","description":"Queue a failed delivery to be sent again, with its body, its `X-Webhook-Event-Id` and `X-Webhook-Delivery-Id`, so a receiver that got it after all can skip it, and with the full retry schedule. It goes to the webhook's current URL, signed with its current secret, and only while the webhook may deliver it. A redelivery that fails again is recorded as a new failed delivery.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}},{"name":"deliveryId","in":"path","required":true,"description":"The `id` of the failed delivery, from the list","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"queued":{"type":"boolean"},"delivery_id":{"type":["string","null"]}},"required":["queued","delivery_id"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such webhook, or no such failed delivery of it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/board":{"get":{"summary":"Get board data","operationId":"getBoard","description":"Retrieve tickets grouped by status. `lanes` has one entry per workflow status of the project, in board order: the status (id, label, category), its ticket count, and its tickets.\n\nA lane holds at most 30 tickets, so its `count` can be larger than its `tickets`; use `GET /tickets` with `status` to read them all. A terminal (closed) lane holds only tickets updated in the last 7 days, and its `count` counts only those. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** tickets:read","tags":["Board"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by workflow status id or label (see GET /statuses); repeat it for several. All lanes remain present; other lanes have no tickets.","schema":{"type":["string","integer"]}},{"name":"priority","in":"query","required":false,"description":"Filter by priority. Any other value is refused with 422.","schema":{"type":"string","enum":["low","medium","high","critical"]}},{"name":"assignee","in":"query","required":false,"description":"Filter by the numeric id of the assigned user. `none` keeps the unassigned tickets. Another value is refused with 422.","schema":{"type":"integer"}},{"name":"tag","in":"query","required":false,"description":"Filter by tag id or name, e.g. `12` or `Billing`. Name several by repeating the parameter or separating them with commas (`Billing,Urgent`); a ticket must carry every tag named. A tag the project does not have matches no ticket: the result is empty, not an error.","schema":{"type":"string"}},{"name":"status_category","in":"query","required":false,"description":"Filter by status category. Name several by repeating the parameter or separating them with commas; `initial,active,paused` is every ticket that is not closed. Another value is refused with 422.","schema":{"type":"string","enum":["initial","active","paused","terminal"]}},{"name":"ticket_type_id","in":"query","required":false,"description":"Filter by ticket type id (see `GET /ticket-types`).","schema":{"type":"integer","minimum":1}},{"name":"created_after","in":"query","required":false,"description":"Tickets created at or after this ISO 8601 date or time (a time without an offset is UTC).","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","required":false,"description":"Tickets created before this ISO 8601 date or time, the moment itself excluded.","schema":{"type":"string","format":"date-time"}},{"name":"updated_after","in":"query","required":false,"description":"Tickets changed at or after this ISO 8601 date or time: for catching up after missed webhook deliveries, with `sort=+updated_at`.","schema":{"type":"string","format":"date-time"}},{"name":"external_id","in":"query","required":false,"description":"The ticket with this `external_id`, compared exactly (case included).","schema":{"type":"string"}},{"name":"updated_before","in":"query","required":false,"description":"Tickets last changed before this ISO 8601 date or time, the moment itself excluded.","schema":{"type":"string","format":"date-time"}},{"name":"per_lane","in":"query","required":false,"description":"Tickets per lane. A lane with more says so in `has_more`; `GET /tickets?status=` with the lane's status id lists all of them, page by page. Another value is refused with 422.","schema":{"type":"integer","minimum":1,"maximum":200,"default":30}}],"responses":{"200":{"description":"Board data with tickets grouped by status","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"lanes":{"type":"array","description":"One lane per workflow status, in board order.","items":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/WorkflowStatus"},"count":{"type":"integer","description":"Tickets in this lane that match, which can be more than `tickets` holds (at most `per_lane`). For a closed lane, only those changed in the last `updated_within_days` days."},"has_more":{"type":"boolean","description":"Whether the lane holds more tickets than `tickets`: `count` is larger. List them with `GET /tickets?status=` and the lane's status id."},"updated_within_days":{"type":["integer","null"],"description":"For a closed (terminal) lane: it shows only tickets changed within this many days, and `count` counts only those. Null for every other lane. `GET /tickets?status=` lists every closed ticket."},"tickets":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/Ticket"}}},"required":["status","count","has_more","updated_within_days","tickets"]}},"total":{"type":"integer","description":"The sum of the lane counts"},"per_lane":{"type":"integer","description":"The most tickets a lane carries in this response"},"ignored_parameters":{"type":"array","items":{"type":"string"},"description":"Present only when the request carried query parameters this endpoint does not define: their names. They were ignored. From 2027-02-01 such a request is answered with 422 instead."}},"required":["lanes","total","per_lane"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Bad request (missing project context)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation failed: an unknown status or other filter value, a `per_lane` outside 1 to 200, more tags named than a ticket can carry, or (from 2027-02-01) a query parameter the endpoint does not define","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/tickets/{ticketId}/comments":{"get":{"summary":"List comments","operationId":"listComments","description":"Retrieve comments for a ticket in chronological order (oldest first), `limit` at a time (1,000 unless it says fewer). While `has_more` is true, ask for the next page with `after_id` set to `next_after_id`. Internal notes are included by default for authenticated team members.\n\nEach comment names its `author`. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** comments:read","tags":["Comments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"ticketId","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"include_internal","in":"query","required":false,"description":"Internal notes (team-only) are included unless this is `false` or `0`. Any other value is refused with 422.","schema":{"type":"string","enum":["true","false","1","0"],"default":"true"}},{"name":"limit","in":"query","required":false,"description":"Comments per page. Another value is refused with 422.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000}},{"name":"after_id","in":"query","required":false,"description":"The page after this comment: the `next_after_id` of the page before. An id that is no comment of the ticket is refused with 422.","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"List of comments","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}},"count":{"type":"integer","description":"Comments on this page"},"has_more":{"type":"boolean","description":"Whether later comments follow"},"next_after_id":{"type":["integer","null"],"description":"The `after_id` of the next page; null on the last"},"ignored_parameters":{"type":"array","items":{"type":"string"},"description":"Present only when the request carried query parameters this endpoint does not define: their names. They were ignored. From 2027-02-01 such a request is answered with 422 instead."}},"required":["comments","count","has_more","next_after_id"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:read"]},"post":{"summary":"Create a comment","operationId":"createComment","description":"Add a comment to a ticket. Comments default to internal notes (team-only). Set `is_internal` to false for customer-visible comments.\n\n**Required Scopes:** comments:write","tags":["Comments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"ticketId","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","maxLength":50000,"description":"Comment text, as Markdown. Rendered to `content_html` unless `content_html` is sent."},"content_html":{"type":["string","null"],"maxLength":200000,"description":"The comment as HTML (sanitized server-side). When sent, it is stored in place of the rendering of `content`."},"is_internal":{"type":"boolean","default":true,"description":"Whether this is an internal note (true) or public comment (false)"}}}}}},"responses":{"201":{"description":"Comment created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}},"required":["comment"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:write"]}},"/comments/{id}":{"get":{"summary":"Get a comment","operationId":"getComment","description":"Retrieve a single comment by ID. The comment must belong to a ticket within the API key's project scope.\n\n**Required Scopes:** comments:read","tags":["Comments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"Comment details","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}},"required":["comment"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket or comment ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:read"]},"put":{"summary":"Update a comment","operationId":"updateComment","description":"Update an existing comment. Only the author or team leads+ can edit comments. Only provided fields are updated. The comment must belong to a ticket within the API key's project scope.\n\n**Required Scopes:** comments:write","tags":["Comments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","maxLength":50000,"description":"Updated text, as Markdown. Replaces `content_html` with its rendering unless `content_html` is sent."},"content_html":{"type":["string","null"],"maxLength":200000,"description":"Updated HTML (sanitized server-side)"},"is_internal":{"type":"boolean","description":"Change visibility (cannot convert public to internal)"}}}}}},"responses":{"200":{"description":"Comment updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}},"required":["comment"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket or comment ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Cannot edit another user's comment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:write"]},"delete":{"summary":"Delete a comment","operationId":"deleteComment","description":"Delete a comment. Only the author or admins can delete comments. The comment must belong to a ticket within the API key's project scope.\n\n**Required Scopes:** comments:write","tags":["Comments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"Comment deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Comment deleted"}}}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket or comment ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Cannot delete another user's comment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:write"]}},"/webhooks":{"get":{"summary":"List webhooks","operationId":"listWebhooks","description":"List the webhooks of the key's owner, in every project they are in. A key scoped to a project lists only that project's webhooks. `project_id` and `project` do not narrow the list.\n\n**Required Scopes:** tickets:read","tags":["Webhooks"],"responses":{"200":{"description":"List of webhook subscriptions","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"count":{"type":"integer"}},"required":["webhooks","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Create a webhook","operationId":"createWebhook","description":"Create a new webhook subscription. Fesk will send POST requests to the specified URL when subscribed events occur.\n\n**Available events:** `ticket.created`, `ticket.updated`, `ticket.deleted`, `ticket.status_changed`, `ticket.assigned`, `ticket.merged`, `comment.created`\n\n`comment.created` is sent for public comments, not for internal notes. It carries the comment's content, so subscribing to it also takes `comments:read` (or `comments:write`); without it the request answers 403.\n\n**Delivery guarantees:** Deliveries are queued and retried, but not guaranteed: an event is lost if it cannot be queued or if every attempt fails. Your endpoint should be idempotent because duplicate deliveries are possible.\n\n**Delivery payload format:**\n```json\n{\"id\": \"evt_\u2026\", \"event\": \"ticket.created\", \"timestamp\": 1717600000, \"data\": {\"ticket\": {...}}}\n```\n`data.ticket` is the ticket, in every event. The fields of the event are beside it: `data.comment` for `comment.created`, `old_status` and `new_status` for `ticket.status_changed`, and so on.\n\n**Delivery headers and signature:** Headers: `X-Webhook-Event` (the event type), `X-Webhook-Event-Id` (as `id`), `X-Webhook-Delivery-Id` (this event to this webhook, the same on every retry: deduplicate on it), `X-Webhook-Attempt` (1 to 5), `X-Webhook-Timestamp` (Unix seconds this attempt was sent), `X-Webhook-Signature-V2` (hex HMAC-SHA256 of `{X-Webhook-Timestamp}.{body}` with the webhook secret: verify it and refuse a timestamp more than a few minutes old) and `X-Webhook-Signature` (hex HMAC-SHA256 of the body alone, kept for receivers that check only it). Compare in constant time. The secret is returned at creation time (auto-generated if not supplied).\n\n**Retry policy:** A delivery fails when the endpoint answers with a status outside 2xx or does not answer within 10 seconds. It is attempted up to 5 times: once, then again 1 minute, 5 minutes, 30 minutes and 2 hours after the attempt before it. A worker that runs once a minute sends the deliveries, so each one leaves up to a minute after it is due. After the last attempt the delivery is recorded as failed; the subscription stays active.\n\n**Event ordering:** Not guaranteed across concurrent deliveries. Use `timestamp` and resource `version` to determine recency.\n\nSee the `WebhookDelivery` schema for the full payload structure.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"Delivery URL, `https` recommended (`http` is accepted). A URL that targets a private or internal address is refused with 422."},"events":{"type":"array","items":{"type":"string","enum":["ticket.created","ticket.updated","ticket.deleted","ticket.status_changed","ticket.assigned","ticket.merged","comment.created"]},"description":"Event types to subscribe to"},"project_id":{"type":"integer","description":"The project whose events the webhook receives. Not needed with a key scoped to a project, which always uses its own; a key without a project names one here, as `project_id` in the query or as the `project` slug, or the request answers 400."},"secret":{"type":["string","null"],"description":"HMAC secret for signature verification (auto-generated if omitted)"}}}}}},"responses":{"201":{"description":"Webhook created successfully. The response includes the signing secret (shown only once).","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/WebhookCreated"}},"required":["webhook"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Project ID is required: the key has no project and the request names none","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks `tickets:write`, or subscribes to `comment.created` without `comments:read`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/webhooks/{id}/test":{"post":{"summary":"Test a webhook","operationId":"testWebhook","description":"Send one test request to the webhook's URL and get the receiver's status back. The request is made while this call waits (up to 10 seconds), signed and with the headers of a real delivery, so a signature check can be tried. Its event is `webhook.test`, which no subscription names, and its body holds no ticket:\n```json\n{\"event\":\"webhook.test\",\"timestamp\":1717600000,\"data\":{\"webhook_id\":3,\"project_id\":1,\"message\":\"Test delivery. No ticket was created or changed.\"}}\n```\nThe answer is `200` whenever the test ran; `delivered` says whether the receiver answered with a 2xx status. A test is not queued, not retried and not counted among failed deliveries, and it is sent to a deactivated webhook too. A key tests the webhooks of its owner, and a key pinned to a project those of that project. Limited to 10 requests per minute.\n\n**Required Scopes:** tickets:write","tags":["Webhooks"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Webhook ID","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"200":{"description":"The test ran. `delivered` says how it went.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"test":{"type":"object","properties":{"delivered":{"type":"boolean","description":"The receiver answered with a 2xx status","example":true},"status_code":{"type":["integer","null"],"description":"The status the receiver answered with, or null when there was no answer or nothing was sent","example":200},"duration_ms":{"type":"integer","description":"How long the request took","example":142},"reason":{"type":["string","null"],"enum":["http_error","no_response","blocked","account_inactive",null],"description":"Why the test was not delivered: `http_error` (a status outside 2xx), `no_response` (no answer within the timeout, or the connection failed), `blocked` (the URL targets a private or internal address; nothing was sent), `account_inactive` (nothing was sent). Null when delivered.","example":null}},"required":["delivered","status_code","duration_ms","reason"]}},"required":["test"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid webhook ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/me":{"get":{"summary":"Describe the calling API key","operationId":"getMe","description":"The calling key's name, prefix, scopes, project pin and expiry, and its owner with the accounts they belong to and their role in each.\n\nRequires only a valid API key, no specific scope. A key pinned to a project lists only that project's account. The owner's role is informational: what a key may do is decided by its scopes, never by the role.","tags":["Projects"],"responses":{"200":{"description":"The key and its owner","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"api_key":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","example":"CI sync"},"prefix":{"type":"string","example":"ld_abc12345"},"scopes":{"type":"array","items":{"type":"string"},"example":["tickets:read","comments:read"]},"project":{"type":["object","null"],"description":"The project the key is pinned to, or null for a key that reaches every project of its owner","properties":{"id":{"type":"integer"},"slug":{"type":["string","null"],"example":"DEV"},"name":{"type":["string","null"]}}},"expires_at":{"type":["string","null"],"format":"date-time","description":"Null for a key that does not expire"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","prefix","scopes","project","expires_at","created_at"]},"owner":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"role":{"type":"string","example":"agent"}},"required":["id","role"]}}},"required":["id","name","accounts"]}},"required":["api_key","owner"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized: a missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/projects":{"get":{"summary":"List projects","operationId":"listProjects","description":"List the projects (workspaces) the API key owner can access.\n\nUse this to discover each project's `slug` and numeric `id`, so requests can reference a project by its human-readable slug (`?project=infrastructure`) instead of an opaque numeric ID. A project-scoped key returns only its own project.\n\nRequires only a valid API key, no specific scope.","tags":["Projects"],"responses":{"200":{"description":"List of accessible projects","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"count":{"type":"integer"}},"required":["projects","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/ticket-types":{"get":{"summary":"List ticket types","operationId":"listTicketTypes","description":"Retrieve available ticket types for the API key's project. Each ticket type includes a `field_schema` array describing the custom fields available for that type.\n\nThe project may be supplied via the API key's scope, a numeric `project_id`, or a `project` slug (e.g. `?project=infrastructure`, discover slugs via `GET /projects`).\n\nUse these to populate dynamic forms and validate `custom_fields` when creating tickets.\n\n**Required Scopes:** tickets:read","tags":["Ticket Types"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of ticket types with their field schemas","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ticket_types":{"type":"array","items":{"$ref":"#/components/schemas/TicketType"}},"count":{"type":"integer"}},"required":["ticket_types","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Project ID is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/statuses":{"get":{"summary":"List workflow statuses","operationId":"listStatuses","description":"List the project's workflow statuses in board order.\n\nWherever the API expects a status (`status` in `PUT /tickets/{id}`, `PATCH /tickets/{id}/status`, `POST /tickets/bulk-update`, the `status` filter), send the status's `id` or its `label`. Labels are what people call a status (\"move it to In Progress\") and match ignoring case and punctuation; ids are stable across renames, so store those. `category` says what a status means: `initial` (new tickets), `active` (being worked on), `paused` (waiting; SLA timer paused) or `terminal` (closed/resolved).\n\nThe project may be supplied via the API key's scope, a numeric `project_id`, or a `project` slug.\n\n**Required Scopes:** tickets:read","tags":["Statuses"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}}],"responses":{"200":{"description":"Workflow statuses in board order","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"statuses":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowStatus"}},"count":{"type":"integer"}},"required":["statuses","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Project ID is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/users":{"get":{"summary":"List users","operationId":"listUsers","description":"List users in the account. Useful for populating the `assigned_to` field when creating or updating tickets.\n\n**Required Scopes:** tickets:read","tags":["Users"],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"count":{"type":"integer"}},"required":["users","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/tickets/{id}/attachments":{"get":{"summary":"List attachments","operationId":"listAttachments","description":"List all attachments for a ticket.\n\n**Required Scopes:** tickets:read","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}}],"responses":{"200":{"description":"List of attachments","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}}},"required":["attachments"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Upload attachment","operationId":"uploadAttachment","description":"Upload a file attachment to a ticket. Uses multipart/form-data with the file in a field named `file`.\n\nThe type is detected from the file's content, not from its name or the upload's `Content-Type`. By default a file holds at most 10 MB, and a ticket at most 20 files and 100 MB together. The accepted types by default: JPEG, PNG, GIF and WebP images, PDF, plain text, CSV, Word (`.doc`, `.docx`), Excel (`.xls`, `.xlsx`), ZIP, 7z and gzip. An SVG is always refused, since it can carry script. A file outside the limits or the types answers `422`.\n\n**Required Scopes:** tickets:write","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The file to upload"}}}}}},"responses":{"201":{"description":"Attachment uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}},"required":["attachment"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No `file` field, an incomplete upload, or a file larger than the server accepts at all","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed: the file is too large, of a type that is not accepted (SVG always), or the ticket has reached its number of files or its total size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/attachments/{id}/download":{"get":{"summary":"Download attachment","operationId":"downloadAttachment","description":"Download an attachment file. Returns the raw file binary with appropriate Content-Type and Content-Disposition headers.\n\n**Response headers:**\n- `Content-Type`: The file's MIME type (e.g., `image/png`, `application/pdf`)\n- `Content-Disposition`: `attachment; filename=\"original.png\"; filename*=UTF-8''original.png`\n- `Content-Length`: File size in bytes\n- `Accept-Ranges`: `bytes`\n- `Cache-Control`: `private, no-cache`\n\nThe response body is the raw file bytes (not JSON). Save the response directly to disk.\n\nA single byte range in `Range` (`bytes=0-1023`, `bytes=1024-`, `bytes=-512`) answers 206 with that part and `Content-Range`; several ranges or a malformed header are ignored and the whole file is sent.\n\n**Required Scopes:** tickets:read","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Attachment ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"File binary stream with Content-Disposition header","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"206":{"description":"The byte range the Range header asked for, with Content-Range","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid attachment ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Attachment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"416":{"description":"The Range header starts past the end of the file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/attachments/{id}":{"delete":{"summary":"Delete attachment","operationId":"deleteAttachment","description":"Delete an attachment from a ticket.\n\n**Required Scopes:** tickets:write","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Attachment ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"Attachment deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Attachment deleted"}},"required":["message"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid attachment ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Attachment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/comments/{id}/attachments":{"get":{"summary":"List comment attachments","operationId":"listCommentAttachments","description":"List the files attached to a comment. Download one with `GET /attachments/{id}/download`.\n\n**Required Scopes:** comments:read","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"The comment's attachments","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}}},"required":["attachments"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Comment not found, or of another project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:read"]},"post":{"summary":"Attach a file to a comment","operationId":"uploadCommentAttachment","description":"Attach a file to a comment the API key's owner wrote, as multipart/form-data with the file in a field named `file`. Types and sizes are those of a ticket's upload; a comment holds at most 20 files. Another user's comment answers `403`.\n\n**Required Scopes:** comments:write","tags":["Attachments"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The file to attach"}}}}}},"responses":{"201":{"description":"The file is attached","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}},"required":["attachment"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No file in the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The comment is another user's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Comment not found, or of another project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"The file is too large, of a type not accepted, or the comment holds 20 files","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["comments:write"]}},"/tags":{"get":{"summary":"List tags","operationId":"listTags","description":"List the project's tags, ordered by name. A tag is a free-form label on tickets; this list is where the ids for `PUT /tickets/{id}/tags` come from.\n\nThe project may be supplied via the API key's scope, a numeric `project_id`, or a `project` slug.\n\n**Required Scopes:** tickets:read","tags":["Tags"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}}],"responses":{"200":{"description":"The project's tags","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"count":{"type":"integer"}},"required":["tags","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Project ID is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/tickets/{id}/tags":{"get":{"summary":"List the tags on a ticket","operationId":"listTicketTags","description":"List the tags on a ticket, ordered by name. It is the same list as the `tags` array of the ticket itself. The ticket's own project decides access, so a request that names the ticket by its id needs no `project_id` or `project` parameter. A ticket number in the path is looked up in the key's project, or in the one these parameters name.\n\n**Required Scopes:** tickets:read","tags":["Tags"],"parameters":[{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"Project slug, an alternative to `project_id`. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"string"}}],"responses":{"200":{"description":"The ticket's tags","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"count":{"type":"integer"}},"required":["tags","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key lacks the required scope, or its owner has no access to the ticket's project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Add a tag to a ticket","operationId":"addTicketTag","description":"Add one tag to a ticket by name. A name that is new to the project creates the tag; a name the project already has, compared ignoring case, reuses that tag. Adding a tag the ticket already carries changes nothing. A ticket holds at most 25 tags.\n\nThe ticket's own project decides access, so a request that names the ticket by its id needs no `project_id` or `project` parameter. A ticket number in the path is looked up in the key's project, or in the one these parameters name.\n\n**Required Scopes:** tickets:write","tags":["Tags"],"parameters":[{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"Project slug, an alternative to `project_id`. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":50,"description":"Tag name, without commas. Surrounding whitespace is trimmed and inner whitespace collapsed.","example":"Billing"}}}}}},"responses":{"200":{"description":"The ticket's tags after the change","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"count":{"type":"integer"}},"required":["tags","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key lacks the required scope, or its owner has no access to the ticket's project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed: `name` is missing, empty, too long or contains a comma, or the ticket already has the maximum number of tags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]},"put":{"summary":"Replace a ticket's tags","operationId":"replaceTicketTags","description":"Set the complete list of tags on a ticket, by tag id (`GET /tags` lists the project's tags). Tags that are not in the list are removed from the ticket; an empty list removes all of them. A ticket holds at most 25 tags.\n\nThe ticket's own project decides access, so a request that names the ticket by its id needs no `project_id` or `project` parameter. A ticket number in the path is looked up in the key's project, or in the one these parameters name.\n\n**Required Scopes:** tickets:write","tags":["Tags"],"parameters":[{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"Project slug, an alternative to `project_id`. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tag_ids"],"properties":{"tag_ids":{"type":"array","items":{"type":"integer"},"description":"Ids of the tags the ticket is to carry. Each must be a tag of the ticket's project.","example":[1,7]}}}}}},"responses":{"200":{"description":"The ticket's tags after the change","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"count":{"type":"integer"}},"required":["tags","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key lacks the required scope, or its owner has no access to the ticket's project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed: `tag_ids` is not a list of tag ids, has too many entries, or names a tag that is not in the ticket's project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/tickets/{id}/tags/{tagId}":{"delete":{"summary":"Remove a tag from a ticket","operationId":"removeTicketTag","description":"Remove one tag from a ticket. The tag itself stays in the project and on its other tickets. `removed` is `false` when the ticket did not carry the tag.\n\nThe ticket's own project decides access, so a request that names the ticket by its id needs no `project_id` or `project` parameter. A ticket number in the path is looked up in the key's project, or in the one these parameters name.\n\n**Required Scopes:** tickets:write","tags":["Tags"],"parameters":[{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"project_id","in":"query","required":false,"description":"Project ID. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"Project slug, an alternative to `project_id`. Needed only when the path names the ticket by its number and the API key is not scoped to a project.","schema":{"type":"string"}},{"name":"tagId","in":"path","required":true,"description":"Tag ID","schema":{"type":"integer"}}],"responses":{"200":{"description":"The ticket's tags after the change","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"removed":{"type":"boolean","description":"Whether the ticket carried the tag"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"count":{"type":"integer"}},"required":["removed","tags","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID or tag ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key lacks the required scope, or its owner has no access to the ticket's project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/tickets/{id}/links":{"get":{"summary":"List ticket links","operationId":"listTicketLinks","description":"List the links of a ticket, newest first. The same list is the `links` field of the ticket. A link to a ticket the API key cannot read is left out.\n\n**Required Scopes:** tickets:read","tags":["Links"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}}],"responses":{"200":{"description":"The ticket's links","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/TicketLink"}},"count":{"type":"integer","example":1}},"required":["links","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID, or no project context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Link a ticket to another","operationId":"createTicketLink","description":"Create a link from the ticket in the path to another ticket. Name that ticket by `linked_ticket_id` or by `linked_ticket_number`. The other direction is written with it: after this ticket `blocks` another, that one lists `blocked_by` this ticket. The ticket in the path must be in the project the request resolves. The linked ticket may be in another project the API key can read; one it cannot read is answered as not found.\n\n**Required Scopes:** tickets:write","tags":["Links"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linked_ticket_id":{"type":"integer","description":"ID of the ticket to link to. Takes precedence over `linked_ticket_number`."},"linked_ticket_number":{"type":"string","description":"Number of the ticket to link to: the full number (`PRJ-9`), or the number within this ticket's project (`9`).","example":"PRJ-9"},"relationship_type":{"type":"string","enum":["related","blocks","blocked_by","duplicates","duplicated_by"],"default":"related","description":"What the ticket in the path is to the linked ticket."}}}}}},"responses":{"201":{"description":"Link created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"link":{"$ref":"#/components/schemas/TicketLink"}},"required":["link"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket ID, or no project context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket or linked ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed (no linked ticket named, unknown relationship type, a ticket linked to itself, or the link already exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/tickets/{id}/links/{linkId}":{"delete":{"summary":"Remove a ticket link","operationId":"deleteTicketLink","description":"Remove a link of the ticket in the path, by the link's `id`. Its other direction, listed on the linked ticket, is removed with it. The ticket in the path must be in the project the request resolves. The linked ticket may be in another project the API key can read; one it cannot read is answered as not found.\n\n**Required Scopes:** tickets:write","tags":["Links"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The ticket: its internal `id` (`42`) or its `ticket_number` (`DEV-12`, the prefix in either case). A number is looked up in the project of the request only, so the number of a ticket in another project answers `404`. A value of digits only is always an id.","schema":{"type":["integer","string"]}},{"name":"linkId","in":"path","required":true,"description":"Link ID (`id` of a link of this ticket)","schema":{"type":"integer"}}],"responses":{"200":{"description":"Link removed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Link deleted"}},"required":["message"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"Invalid ticket or link ID, or no project context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Ticket or link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden, API key lacks the required scope for this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/articles":{"get":{"summary":"List and search knowledge base articles","operationId":"listKbArticles","description":"List the account's articles, most recently changed first, without their content. An unknown `status`, `visibility`, `category` or `ticket` is refused with `422`, not ignored. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`. A query parameter the endpoint does not define is ignored and named in `ignored_parameters`; from 2027-02-01 it is refused with 422.\n\n**Required Scopes:** tickets:read","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Words to find in the title or the content","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"A category by its id or its name","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Only articles in this status","schema":{"type":"string","enum":["draft","pending_review","published"]}},{"name":"visibility","in":"query","required":false,"description":"Only articles with this visibility","schema":{"type":"string","enum":["portal","internal"]}},{"name":"ticket","in":"query","required":false,"description":"Only the articles that cite this ticket: its id or its full number (`DEV-12`)","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Articles per page","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of articles","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"articles":{"type":"array","items":{"$ref":"#/components/schemas/KbArticle"}},"pagination":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"}}}},"required":["articles","pagination"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"422":{"description":"Unknown status, visibility, category or ticket","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Create a knowledge base article","operationId":"createKbArticle","description":"Create a draft article, version 1, authored by the API key's owner. Its slug is made from the title. Nothing the API does shows an article in the customer portal: an article made here is a `draft`, and publishing, approving and changing a published article stay with a person in the app. Once published, the portal shows an article only when its `visibility` is `portal`. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":255,"description":"Surrounding whitespace is trimmed"},"content":{"type":"string","description":"Markdown, at most 65535 bytes of UTF-8 (a character outside ASCII takes two to four); more is refused with `422`."},"category_id":{"type":["integer","null"],"description":"A category by its id; `null` clears it. Takes precedence over `category`."},"category":{"type":"string","description":"A category by its name"},"article_type":{"type":"string","enum":["article","faq"],"default":"article"},"status":{"type":"string","enum":["draft"],"description":"Optional; any other value is refused with `422`."},"visibility":{"type":"string","enum":["portal","internal"],"default":"internal","description":"Whether the customer portal shows the article once a person publishes it. `internal` keeps an approved article in the app."}}}}}},"responses":{"201":{"description":"The new article","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"article":{"$ref":"#/components/schemas/KbArticle"}},"required":["article"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"422":{"description":"Validation failed: a missing, empty or too long title or content, an unknown category, article type or visibility, or a status other than `draft`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/articles/{id}":{"get":{"summary":"Get a knowledge base article","operationId":"getKbArticle","description":"One article with its content and the tickets it cites. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:read","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The article","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"article":{"$ref":"#/components/schemas/KbArticle"}},"required":["article"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"put":{"summary":"Update a knowledge base article","operationId":"updateKbArticle","description":"Change an article's title, content or category, as a new version; a field left out keeps its value. Send the `version` you read last: when the article has changed since, nothing is written and the answer is `409`; read it again, and send your change on top of it.\n\nA `published` article is refused with `409`. The status and the visibility do not change; `pending_review` stays so. Nothing the API does shows an article in the customer portal: an article made here is a `draft`, and publishing, approving and changing a published article stay with a person in the app. Once published, the portal shows an article only when its `visibility` is `portal`. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","minimum":1,"description":"The article's `version` when you read it last"},"title":{"type":"string","maxLength":255,"description":"Surrounding whitespace is trimmed"},"content":{"type":"string","description":"Markdown, at most 65535 bytes of UTF-8 (a character outside ASCII takes two to four); more is refused with `422`."},"category_id":{"type":["integer","null"],"description":"A category by its id; `null` clears it. Takes precedence over `category`."},"category":{"type":"string","description":"A category by its name"}}}}}},"responses":{"200":{"description":"The article after the change","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"article":{"$ref":"#/components/schemas/KbArticle"}},"required":["article"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"409":{"description":"The article changed since `version`, or it is published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed: no `version`, nothing to change, a field as on create, or a `status` or `visibility`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/articles/{id}/versions":{"get":{"summary":"List an article's versions","operationId":"listKbArticleVersions","description":"The versions of an article, newest first, without their content. The first is the current one. Every update writes a version; the oldest are dropped beyond the article's limit (50 by default). The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:read","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The versions","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/KbArticleVersion"}},"count":{"type":"integer"}},"required":["versions","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/kb/articles/{id}/versions/{version}":{"get":{"summary":"Get one version of an article","operationId":"getKbArticleVersion","description":"One version with its content, to compare with another. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:read","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}},{"name":"version","in":"path","required":true,"description":"Version number","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"The version","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/KbArticleVersion"}},"required":["version"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found, or a version it does not keep","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]}},"/kb/articles/{id}/submit-review":{"post":{"summary":"Submit an article for review","operationId":"submitKbArticleForReview","description":"Move a `draft` to `pending_review`, where a reviewer approves or rejects it in the app. An article already pending review stays so; a `published` one answers `409`. Nothing the API does shows an article in the customer portal: an article made here is a `draft`, and publishing, approving and changing a published article stay with a person in the app. Once published, the portal shows an article only when its `visibility` is `portal`. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"200":{"description":"The article, pending review","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"article":{"$ref":"#/components/schemas/KbArticle"}},"required":["article"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"409":{"description":"The article is published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/articles/{id}/tickets":{"post":{"summary":"Cite a ticket in an article","operationId":"citeKbArticleTicket","description":"Tie an article to a ticket it came from, such as the decision ticket whose rule it holds. The article lists it under `tickets`, and `GET /kb/articles?ticket=` finds the articles that cite a ticket. The ticket must be in a project of the article's account that the API key reaches; another is answered as not found. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ticket_id":{"type":"integer","description":"The ticket by its id. Takes precedence over `ticket_number`."},"ticket_number":{"type":"string","description":"The ticket by its full number","example":"DEV-313"}}}}}},"responses":{"201":{"description":"The tickets the article cites now","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tickets":{"type":"array","items":{"$ref":"#/components/schemas/KbCitedTicket"}},"count":{"type":"integer"}},"required":["tickets","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"422":{"description":"No ticket named, or the article already cites it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article or ticket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/articles/{id}/tickets/{ticket}":{"delete":{"summary":"Stop citing a ticket","operationId":"removeKbArticleTicket","description":"Untie an article from a ticket. Neither is deleted. The ticket must be in a project of the article's account that the API key reaches; another is answered as not found. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"The article's id, or its slug. Digits are read as the id.","schema":{"type":"string"}},{"name":"ticket","in":"path","required":true,"description":"The ticket's id or its full number (`DEV-313`)","schema":{"type":"string"}}],"responses":{"200":{"description":"The tickets the article cites now","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"tickets":{"type":"array","items":{"$ref":"#/components/schemas/KbCitedTicket"}},"count":{"type":"integer"}},"required":["tickets","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found, or it does not cite the ticket","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}},"/kb/categories":{"get":{"summary":"List knowledge base categories","operationId":"listKbCategories","description":"The account's categories in display order, each with its number of articles. A workspace several repositories share keeps a category per repository, so each finds its own articles. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:read","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}}],"responses":{"200":{"description":"The categories","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/KbCategory"}},"count":{"type":"integer"}},"required":["categories","count"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:read"]},"post":{"summary":"Create a knowledge base category","operationId":"createKbCategory","description":"Create a category. As in the app, only an administrator of the account does: a key whose owner is not one answers `403`. A name is unique in the account, compared ignoring case. The knowledge base belongs to the account of the project the request names (the API key's project, `project_id` or `project`), and covers all of that account's projects. The account must have the knowledge base turned on; otherwise every knowledge base request answers `403` with `error_code` `kb_disabled`.\n\n**Required Scopes:** tickets:write","tags":["Knowledge Base"],"parameters":[{"name":"project_id","in":"query","required":false,"description":"A workspace in the app is a project in the API. Project ID is required when the key is not project-scoped (or supply the `project` slug instead); a project-scoped key supplies it automatically.","schema":{"type":"integer"}},{"name":"project","in":"query","required":false,"description":"A workspace in the app is a project in the API. Its slug (e.g. `infrastructure`) is the `project` parameter, an alternative to `project_id`. Discover slugs via `GET /projects`. Ignored when the API key is scoped to a project.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A value of your own, 1 to 128 characters (a UUID, for one), so a retry is not applied twice. A repeat with the same key, method, path and body returns the first successful answer, marked `X-Idempotency-Replay: true`, for 90 days; the same key with another body, or while the first request is still running, answers `409`. Only a 2xx answer is stored. A key outside 1 to 128 characters answers `400`.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":100,"example":"Sensi"},"sort_order":{"type":"integer","minimum":0,"default":0}}}}}},"responses":{"201":{"description":"The new category","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/KbCategory"}},"required":["category"]}},"required":["ok","data"]}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}},"409":{"description":"The account already has a category of that name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"422":{"description":"Validation failed: a missing or too long name, or a negative sort order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"description":"No project named, or an invalid article id or slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key lacks the scope or the project, or the account has the knowledge base turned off (`error_code` `kb_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"404":{"description":"Article not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests, rate limit exceeded. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"402":{"description":"Payment required, free API allowance exhausted or monthly call limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Allowance-Limit":{"$ref":"#/components/headers/X-Allowance-Limit"},"X-Allowance-Remaining":{"$ref":"#/components/headers/X-Allowance-Remaining"},"X-Allowance-Reset":{"$ref":"#/components/headers/X-Allowance-Reset"},"X-Allowance-Warning":{"$ref":"#/components/headers/X-Allowance-Warning"}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"x-scopes":["tickets:write"]}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key passed as a Bearer token in the Authorization header. Generate a key from Settings \u2192 API Keys."},"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"The same API key in the X-API-Key header, as an alternative to the Authorization header. A key in the `api_key` query parameter is refused with `400`."}},"schemas":{"Ticket":{"type":"object","properties":{"id":{"type":"integer","example":1},"project_id":{"type":"integer","example":1},"ticket_type_id":{"type":"integer","example":1},"project_ref_id":{"type":"integer","example":1,"description":"Sequential ID within the project"},"ticket_number":{"type":"string","example":"PRJ-1","description":"Human-readable ticket number"},"external_id":{"type":["string","null"],"maxLength":255,"description":"Another system's id for the ticket, set with `external_id` on create or update; unique among the project's tickets that are not deleted. Find a ticket by it with `GET /tickets?external_id=`. A connector or an import may set it too."},"title":{"type":"string","example":"Login page not loading"},"description":{"type":["string","null"],"example":"Detailed description"},"description_html":{"type":["string","null"],"description":"The description as sanitized HTML, as the app shows it: `description` rendered from Markdown when it was written through the API, or what the app's editor wrote. Null without a description.","example":"<p>Detailed description</p>"},"status":{"type":"string","description":"Status label, the name people see in the app. Projects define their own statuses and may rename them, so store `status_id`, not the label. `GET /statuses` lists them.","example":"In Progress"},"status_id":{"type":["integer","null"],"description":"Stable status id; send it (or the label) wherever a status is expected.","example":143},"status_category":{"type":["string","null"],"enum":["initial","active","paused","terminal",null],"description":"What the status means: initial (new), active (being worked on), paused (waiting, SLA timer paused), terminal (closed/resolved).","example":"active"},"priority":{"type":"string","enum":["low","medium","high","critical"],"example":"medium"},"priority_label":{"type":"string","example":"Medium"},"assigned_to":{"type":["integer","null"],"example":5},"created_by":{"type":"integer","example":1},"updated_by":{"type":["integer","null"]},"custom_fields":{"type":"object"},"tags":{"type":"array","description":"The tags on the ticket, ordered by name. Set them by name with `tags` when creating or updating the ticket.","items":{"type":"object","properties":{"id":{"type":"integer","example":12},"project_id":{"type":"integer","example":1},"name":{"type":"string","example":"Billing"},"slug":{"type":"string","description":"The lowercased name tags are matched by","example":"billing"},"color":{"type":["string","null"],"example":"#4f46e5"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}},"source_type":{"type":"string","enum":["manual","email","api","webhook","automation"],"description":"How the ticket was created: manual (web UI), email (inbound email), api (external API), webhook (source connector), automation (automation rule or workflow hand-off)","example":"api"},"merged_into_id":{"type":["integer","null"]},"version":{"type":"integer","example":1,"description":"Optimistic concurrency version"},"resolution_type":{"type":["string","null"],"enum":["resolved","wont_fix","duplicate",null],"description":"How the ticket was closed: resolved, wont_fix or duplicate. Null while open; cleared when reopened."},"workflow_signals":{"type":["object","null"],"description":"AI triage signals. Null when none are available; each property is optional. These are signals, not the ticket's current fields.","properties":{"ai_category":{"type":"string","description":"Suggested ticket category"},"llm_confidence":{"type":"number","description":"Confidence reported by triage"},"ai_priority":{"type":"string","description":"Suggested priority"},"ai_language":{"type":"string","description":"Detected language"}},"additionalProperties":false},"links":{"type":"array","items":{"$ref":"#/components/schemas/TicketLink"},"description":"Links to other tickets, newest first. A link to a ticket the API key cannot read is left out. Change them through `/tickets/{id}/links`."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","status","priority","project_id","ticket_type_id","created_by","version"]},"Comment":{"type":"object","properties":{"id":{"type":"integer","example":1},"ticket_id":{"type":"integer","example":42},"user_id":{"type":"integer","example":5},"author":{"type":"object","description":"Who wrote the comment. `user`: a Fesk user. `customer`: the ticket's contact, for a reply that came by email (`user_id` is then the e-mail system user's). `system`: another system user, such as the AI agent. `email` is there only for a key that may read tickets. Webhook payloads leave the author out.","properties":{"id":{"type":"integer","description":"The user id, as `user_id`"},"name":{"type":["string","null"]},"type":{"type":"string","enum":["user","customer","system"]},"email":{"type":["string","null"],"format":"email"}},"required":["id","name","type"]},"content":{"type":"string","example":"This is a comment"},"content_html":{"type":["string","null"],"description":"The comment as sanitized HTML: the `content_html` that was sent, or `content` rendered from Markdown.","example":"<p>This is a comment</p>"},"is_internal":{"type":"boolean","example":true,"description":"Whether this is an internal note (team-only) or public comment"},"email_message_id":{"type":["string","null"],"description":"Email message ID if comment originated from email. Sent in webhook payloads; API responses leave it out."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","ticket_id","user_id","content","is_internal"]},"Attachment":{"type":"object","properties":{"id":{"type":"integer","example":7},"ticket_id":{"type":"integer","example":42},"comment_id":{"type":["integer","null"],"description":"Always null for the files the API lists and uploads, which are those of the ticket itself"},"filename":{"type":"string","description":"Stored file name"},"original_filename":{"type":"string","example":"screenshot.png"},"mime_type":{"type":"string","description":"Detected from the file content, not taken from the upload","example":"image/png"},"size_bytes":{"type":"integer","example":84521},"formatted_size":{"type":"string","example":"82.54 KB"},"is_image":{"type":"boolean","example":true},"is_embedded":{"type":"boolean","description":"Whether the file is shown inline in a description or comment"},"download_url":{"type":["string","null"],"description":"The path to download the file from with the same key (`GET /attachments/{id}/download`), relative to the API host","example":"/v1/attachments/7/download"},"created_by":{"type":"integer","example":5},"created_at":{"type":"string","format":"date-time","example":"2026-04-11T14:31:00.000000Z"},"deleted_at":{"type":["string","null"],"format":"date-time"},"deleted_by":{"type":["integer","null"]}},"required":["id","ticket_id","original_filename","mime_type","size_bytes","created_at"]},"User":{"type":"object","properties":{"id":{"type":"integer","example":5},"name":{"type":"string","example":"Jane Smith"},"email":{"type":"string","format":"email","example":"jane@example.com"},"role":{"type":"string","enum":["admin","team_lead","agent"],"example":"agent"},"is_active":{"type":"boolean","example":true}},"required":["id","name","email","role","is_active"]},"Webhook":{"type":"object","description":"Webhook subscription (list representation, secret is never included)","properties":{"id":{"type":"integer","example":1},"user_id":{"type":"integer","description":"Owner of the webhook: the user whose key created it","example":5},"project_id":{"type":"integer","example":3},"api_key_id":{"type":["integer","null"],"description":"The API key the webhook was made with, null when made in the app. Such a webhook delivers only while that key is valid and only the events its current scopes may read.","example":12},"url":{"type":"string","format":"uri","example":"https://example.com/webhook"},"events":{"type":"array","items":{"type":"string"},"example":["ticket.created","ticket.updated"]},"is_active":{"type":"boolean","example":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","user_id","project_id","url","events","is_active","created_at","updated_at"]},"WebhookCreated":{"type":"object","description":"Webhook subscription (create response, includes one-time secret)","properties":{"id":{"type":"integer","example":1},"user_id":{"type":"integer","description":"Owner of the webhook: the user whose key created it","example":5},"project_id":{"type":"integer","example":3},"api_key_id":{"type":["integer","null"],"description":"The API key the webhook was made with, null when made in the app. Such a webhook delivers only while that key is valid and only the events its current scopes may read.","example":12},"url":{"type":"string","format":"uri","example":"https://example.com/webhook"},"events":{"type":"array","items":{"type":"string"},"example":["ticket.created","ticket.updated"]},"is_active":{"type":"boolean","example":true},"secret":{"type":"string","description":"HMAC signing secret, returned ONLY in this create response. Store it securely.","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","user_id","project_id","url","events","is_active","secret","created_at","updated_at"]},"PaginationMeta":{"type":"object","properties":{"total":{"type":"integer","example":150},"page":{"type":"integer","example":1},"per_page":{"type":"integer","example":50},"total_pages":{"type":"integer","example":3}},"required":["total","page","per_page","total_pages"]},"ErrorResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"integer","example":422},"message":{"type":"string","example":"Validation failed"},"error_code":{"type":"string","enum":["bad_request","api_key_in_query","invalid_idempotency_key","api_version_required","api_version_unsupported","authentication_required","invalid_api_key","api_key_expired","api_key_revoked","api_key_owner_inactive","payment_required","billing_required","limit_reached","forbidden","insufficient_scope","kb_disabled","not_found","method_not_allowed","conflict","version_conflict","idempotency_key_reused","idempotency_in_progress","external_id_taken","payload_too_large","unsupported_media_type","validation_failed","TRANSITION_NOT_ALLOWED","rate_limited","internal_error","service_unavailable"],"description":"Stable machine-readable reason, on every error. The more precise codes are listed; an error none of them names carries the code of its status (bad_request, authentication_required, payment_required, forbidden, not_found, conflict, validation_failed, rate_limited, internal_error, ...). A 402 also carries it in validation_errors.error_code. A code added later is treated like the generic code of its status."},"required_scopes":{"type":"array","items":{"type":"string"},"description":"Present for insufficient_scope: alternative scopes accepted by the route. Any one is sufficient; * grants full access."},"validation_errors":{"type":["object","null"],"description":"Field-level validation errors (keys are field names, values are arrays of error messages) or structured details for non-validation errors (e.g. billing: error_code, used, limit).","additionalProperties":true}},"required":["code","message"]}},"required":["ok","error"]},"SuccessResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"object"}},"required":["ok","data"]},"WebhookDelivery":{"type":"object","description":"Body of the HTTP POST sent to a webhook subscriber URL. Headers: `X-Webhook-Event` (the event type), `X-Webhook-Event-Id` (as `id`), `X-Webhook-Delivery-Id` (this event to this webhook, the same on every retry: deduplicate on it), `X-Webhook-Attempt` (1 to 5), `X-Webhook-Timestamp` (Unix seconds this attempt was sent), `X-Webhook-Signature-V2` (hex HMAC-SHA256 of `{X-Webhook-Timestamp}.{body}` with the webhook secret: verify it and refuse a timestamp more than a few minutes old) and `X-Webhook-Signature` (hex HMAC-SHA256 of the body alone, kept for receivers that check only it).","properties":{"id":{"type":"string","description":"The event's id: the same for every webhook the event reaches and on every retry, also sent as `X-Webhook-Event-Id`","example":"evt_5f0c1e9a7b3d4c2e8f6a1b0c9d8e7f6a"},"event":{"type":"string","enum":["ticket.created","ticket.updated","ticket.deleted","ticket.status_changed","ticket.assigned","ticket.merged","comment.created"],"description":"The event type that triggered this delivery","example":"ticket.created"},"timestamp":{"type":"integer","description":"Unix timestamp (seconds) of the moment the event was queued","example":1717600000},"data":{"type":"object","description":"The ticket under `ticket` for every event, and the fields of the event beside it.","properties":{"ticket":{"$ref":"#/components/schemas/WebhookDeliveryTicketPayload"},"comment":{"$ref":"#/components/schemas/WebhookDeliveryCommentPayload","description":"`comment.created` only"},"actor_id":{"type":"integer","description":"User who made the change: `ticket.updated`, `ticket.status_changed`, `ticket.assigned`"},"deleted_by":{"type":"integer","description":"`ticket.deleted` only"},"old_status":{"type":"string","description":"Label of the previous status: `ticket.status_changed` only","example":"Open"},"old_status_id":{"type":["integer","null"],"description":"`ticket.status_changed` only","example":142},"new_status":{"type":"string","description":"Label of the new status: `ticket.status_changed` only","example":"In Progress"},"new_status_id":{"type":["integer","null"],"description":"`ticket.status_changed` only","example":143},"old_assignee":{"type":["integer","null"],"description":"User id: `ticket.assigned` only"},"new_assignee":{"type":["integer","null"],"description":"User id: `ticket.assigned` only","example":5},"merged_ticket_ids":{"type":"array","items":{"type":"integer"},"description":"Ids of the tickets merged into `ticket`: `ticket.merged` only"},"merged_by":{"type":"integer","description":"`ticket.merged` only"}},"required":["ticket"]}},"required":["id","event","timestamp","data"]},"WebhookDeliveryTicketPayload":{"type":"object","description":"The ticket as `data.ticket` carries it, in every event","properties":{"id":{"type":"integer","example":42},"project_id":{"type":"integer","example":1},"ticket_type_id":{"type":"integer","example":1},"title":{"type":"string","example":"Login page returns 500 error"},"status":{"type":"string","description":"Status label","example":"In Progress"},"status_changed_at":{"type":["string","null"],"format":"date-time"},"position":{"type":"integer","example":0},"priority":{"type":"string","enum":["low","medium","high","critical"],"example":"high"},"custom_fields":{"type":["object","null"],"description":"The fields of the ticket type by name. The description text is `custom_fields.description`."},"assigned_to":{"type":["integer","null"],"example":5},"created_by":{"type":"integer","example":1},"requester_id":{"type":["integer","null"]},"updated_by":{"type":["integer","null"],"example":5},"source_type":{"type":"string","enum":["manual","email","api","webhook","automation"],"example":"manual"},"source_id":{"type":["string","null"]},"external_id":{"type":["string","null"]},"merged_into_id":{"type":["integer","null"]},"project_ref_id":{"type":["integer","null"],"example":42},"ticket_number":{"type":["string","null"],"example":"PRJ-42"},"version":{"type":"integer","example":2},"resolution_type":{"type":["string","null"],"enum":["resolved","wont_fix","duplicate",null]},"deleted_at":{"type":["string","null"],"format":"date-time"},"deleted_by":{"type":["integer","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"description_html":{"type":["string","null"],"description":"The description as sanitized HTML"},"detected_language":{"type":["string","null"],"example":"en"},"workflow_signals":{"type":["object","null"],"description":"AI triage signals, the same four optional fields as the REST ticket. Null when none are available.","properties":{"ai_category":{"type":"string"},"llm_confidence":{"type":"number"},"ai_priority":{"type":"string"},"ai_language":{"type":"string"}},"additionalProperties":false},"status_id":{"type":["integer","null"],"description":"Stable status id","example":143},"status_category":{"type":["string","null"],"enum":["initial","active","paused","terminal",null],"example":"active"}},"required":["id","project_id","title","status","priority"]},"WebhookDeliveryCommentPayload":{"type":"object","description":"The comment as `data.comment` carries it in a `comment.created` event, which is sent for public comments only","properties":{"id":{"type":"integer","example":15},"ticket_id":{"type":"integer","example":42},"user_id":{"type":"integer","example":5},"content":{"type":"string","example":"We have identified the root cause."},"content_html":{"type":["string","null"],"description":"The content as sanitized HTML"},"is_internal":{"type":"boolean","example":false},"email_message_id":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","ticket_id","user_id","content","is_internal"]},"KbArticle":{"type":"object","description":"A knowledge base article. Articles belong to the account, not to one project.","properties":{"id":{"type":"integer","example":41},"slug":{"type":"string","description":"Unique in the account; a path takes it in place of the id","example":"offside-rule"},"title":{"type":"string","maxLength":255,"example":"Offside rule"},"article_type":{"type":"string","enum":["article","faq"],"example":"article"},"status":{"type":"string","enum":["draft","pending_review","published"],"description":"`draft` and `pending_review` are internal. A `published` article is shown in the customer portal when its `visibility` is `portal`; only a person publishes, in the app.","example":"draft"},"visibility":{"type":"string","enum":["portal","internal"],"description":"Whether the customer portal shows the article once it is published. An `internal` article, approved and published or not, stays in the app. Set when the article is made (`internal` by default) and changed in the app.","example":"internal"},"visible_in_portal":{"type":"boolean","description":"Whether the customer portal shows the article: true exactly when `status` is `published` and `visibility` is `portal`","example":false},"version":{"type":"integer","description":"The current version. Send it as `version` with `PUT /kb/articles/{id}`.","example":3},"category":{"type":["object","null"],"properties":{"id":{"type":"integer","example":2},"name":{"type":"string","example":"Sensi"}},"required":["id","name"]},"author":{"type":"object","properties":{"id":{"type":"integer","example":12},"name":{"type":["string","null"],"example":"Ada Lovelace"}},"required":["id","name"],"description":"Who wrote the current version"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"content":{"type":"string","description":"Markdown. Present on a single article, not in the list."},"tickets":{"type":"array","items":{"$ref":"#/components/schemas/KbCitedTicket"},"description":"The tickets the article cites, in the order they were cited, leaving out any the API key cannot reach. Present on a single article, not in the list."}},"required":["id","slug","title","article_type","status","visibility","visible_in_portal","version","category","author","created_at","updated_at"]},"KbArticleVersion":{"type":"object","description":"One version of an article. The current version is the article itself; the others are kept up to the article's limit (50 by default), the oldest dropped first.","properties":{"version":{"type":"integer","example":2},"is_current":{"type":"boolean","example":false},"title":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}},"required":["id","name"]},"created_at":{"type":["string","null"],"format":"date-time","description":"When this version was written"},"content":{"type":"string","description":"Present on a single version, not in the list."}},"required":["version","is_current","title","author","created_at"]},"KbCategory":{"type":"object","properties":{"id":{"type":"integer","example":2},"name":{"type":"string","maxLength":100,"example":"Sensi"},"sort_order":{"type":"integer","example":0},"article_count":{"type":"integer","example":16}},"required":["id","name","sort_order","article_count"]},"KbCitedTicket":{"type":"object","properties":{"id":{"type":"integer","example":88151},"ticket_number":{"type":["string","null"],"example":"DEV-313"},"title":{"type":"string"}},"required":["id","ticket_number","title"]},"FieldDefinition":{"type":"object","description":"A custom field definition within a ticket type schema","properties":{"name":{"type":"string","description":"Field identifier (unique within ticket type, used as key in custom_fields)","example":"customer_email"},"label":{"type":"string","description":"Human-readable display label","example":"Customer Email"},"type":{"type":"string","enum":["text","textarea","number","date","datetime","select","multi_select","boolean","email","url"],"description":"Data type determining validation and rendering","example":"email"},"required":{"type":"boolean","description":"Whether the field is required when creating/updating tickets","example":false},"constraints":{"type":["object","null"],"description":"Validation constraints (varies by field type)","properties":{"min_length":{"type":"integer","description":"Minimum string length (text/textarea)","example":1},"max_length":{"type":"integer","description":"Maximum string length (text/textarea)","example":500},"min_value":{"type":"number","description":"Minimum numeric value (number)","example":0},"max_value":{"type":"number","description":"Maximum numeric value (number)","example":100},"pattern":{"type":"string","description":"Regex pattern for validation (text)","example":"^[A-Z]{2,5}$"},"allowed_values":{"type":"array","items":{"type":"string"},"description":"Allowed values (required for select/multi_select)","example":["Option A","Option B","Option C"]}}},"default_value":{"description":"Default value for the field (type varies by field type)","example":null},"order":{"type":"integer","description":"Display order for form rendering (lower = first)","example":0}},"required":["name","label","type"]},"TicketType":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Bug Report"},"slug":{"type":"string","description":"Slugified name for reference","example":"bug-report"},"description":{"type":["string","null"],"example":"For reporting software bugs"},"is_default":{"type":"boolean","example":false},"is_active":{"type":"boolean","example":true},"field_schema":{"type":"array","items":{"$ref":"#/components/schemas/FieldDefinition"},"description":"Array of custom field definitions for this ticket type"},"display_fields":{"type":"array","items":{"type":"string"},"description":"Field names to display in list/card views","example":["title","priority","customer_email"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","is_default","field_schema"]},"Project":{"type":"object","description":"A project (workspace) the API key owner can access.","properties":{"id":{"type":"integer","description":"Numeric project ID","example":3},"name":{"type":"string","description":"Human-readable project name","example":"Infrastructure"},"slug":{"type":"string","description":"Stable, human-readable identifier. Pass it as the `project` parameter on project-scoped endpoints instead of the numeric `project_id`.","example":"infrastructure"},"description":{"type":["string","null"],"example":"Cloud infrastructure and platform tickets"}},"required":["id","name","slug"]},"WorkflowStatus":{"type":"object","description":"A workflow status of the project. Wherever the API expects a status, send its `id` (stable) or its `label` (the name people see; unique within the project, matched ignoring case and punctuation). Labels change when a status is renamed, so store ids.","properties":{"id":{"type":"integer","description":"Stable status id","example":143},"label":{"type":"string","description":"Display name shown in the app","example":"In Progress"},"category":{"type":"string","enum":["initial","active","paused","terminal"],"description":"Behaviour: initial (new tickets), active (being worked on), paused (waiting, SLA timer paused), terminal (closed/resolved)","example":"active"},"color":{"type":"string","example":"yellow"},"position":{"type":"integer","description":"Board order, ascending","example":1},"is_default":{"type":"boolean","description":"Status new tickets start in"}},"required":["id","label","category","position","is_default"]},"TicketLink":{"type":"object","description":"A link from one ticket to another, as seen from `ticket_id`. A link has two directions that are written and removed together: when A `blocks` B, B lists `blocked_by` A; `related` reads the same from both sides. Each direction has its own `id`.","properties":{"id":{"type":"integer","description":"Link id, used to remove the link","example":12},"ticket_id":{"type":"integer","description":"The ticket that lists this link","example":42},"linked_ticket_id":{"type":"integer","description":"The ticket at the other end","example":37},"relationship_type":{"type":"string","enum":["related","blocks","blocked_by","duplicates","duplicated_by"],"description":"What `ticket_id` is to `linked_ticket_id`","example":"blocked_by"},"relationship_label":{"type":"string","example":"Blocked by"},"linked_ticket":{"type":"object","description":"Summary of the linked ticket. Its status follows the Ticket schema.","properties":{"id":{"type":"integer","example":37},"project_id":{"type":"integer","description":"May differ from the linking ticket's project","example":1},"ticket_number":{"type":["string","null"],"example":"PRJ-9"},"title":{"type":"string","example":"Decide the retention period"},"status":{"type":"string","description":"Status label","example":"Closed"},"status_id":{"type":["integer","null"],"example":147},"status_category":{"type":["string","null"],"enum":["initial","active","paused","terminal",null],"example":"terminal"},"priority":{"type":"string","enum":["low","medium","high","critical"],"example":"medium"}},"required":["id","project_id","title","status","priority"]},"created_by":{"type":"integer","example":5},"created_at":{"type":"string","format":"date-time"}},"required":["id","ticket_id","linked_ticket_id","relationship_type","relationship_label","linked_ticket"]},"RateLimitInfo":{"type":"object","description":"Rate limit information returned in response headers (X-RateLimit-*)","properties":{"limit":{"type":"integer","description":"Maximum requests allowed in the current window","example":100},"remaining":{"type":"integer","description":"Remaining requests in the current window","example":87},"reset":{"type":"integer","description":"Unix timestamp when the rate limit window resets","example":1717600060}},"required":["limit","remaining","reset"]},"Tag":{"type":"object","description":"A free-form label on tickets. Each project has its own tags.","properties":{"id":{"type":"integer","description":"Tag id, as `PUT /tickets/{id}/tags` takes it","example":7},"project_id":{"type":"integer","example":3},"name":{"type":"string","description":"Display name, in the case it was first written","example":"Billing"},"slug":{"type":"string","description":"The name in lower case. Two names with the same slug are the same tag.","example":"billing"},"color":{"type":["string","null"],"description":"Chip color as `#rrggbb`, or null when the app picks one","example":"#4f46e5"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","project_id","name","slug","color","created_at","updated_at"]}},"headers":{"X-Allowance-Limit":{"description":"The month's API call allowance: the free calls, or the monthly call limit with paid API usage. Absent when there is no ceiling.","schema":{"type":"integer","minimum":0}},"X-Allowance-Remaining":{"description":"API calls left before 402, free calls granted to the account included. Absent when there is no ceiling.","schema":{"type":"integer","minimum":0}},"X-Allowance-Reset":{"description":"Unix timestamp at which the month's call count starts again. Absent when there is no ceiling.","schema":{"type":"integer"}},"X-Allowance-Warning":{"description":"`low` once 10% of the allowance or less is left, `exhausted` when nothing is; absent otherwise.","schema":{"type":"string","enum":["low","exhausted"]}},"X-RateLimit-Limit":{"description":"Requests allowed in the window.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Remaining":{"description":"Requests left in the window.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Reset":{"description":"Unix timestamp of the time of the response plus the window length. The window slides, so this is the latest time by which the count has fully recovered, not when it starts again.","schema":{"type":"integer"}}}},"tags":[{"name":"Tickets"},{"name":"Webhooks"},{"name":"Board"},{"name":"Comments"},{"name":"Projects"},{"name":"Ticket Types"},{"name":"Statuses"},{"name":"Users"},{"name":"Attachments"},{"name":"Tags"},{"name":"Links"},{"name":"Knowledge Base"}],"x-reference-sections":[{"title":"Authentication & Scopes","content":"Send the API key in the `Authorization: Bearer <key>` header or in the `X-API-Key` header, on the `api.` host only. A key in the `api_key` query parameter is refused with `400`; a missing, unknown, revoked or expired key (keys can have an `expires_at`) answers `401`.\n\n| Scope | Grants |\n|-------|--------|\n| `tickets:read` | Reading tickets, tags, links, attachments, the board, ticket types, statuses, users, webhooks and the knowledge base |\n| `tickets:write` | Creating and changing tickets, tags, links, attachments, webhooks and knowledge base articles and categories |\n| `tickets:delete` | Deleting tickets |\n| `comments:read` | Reading comments |\n| `comments:write` | Creating, changing and deleting comments |\n| `*` | Everything, including the routes no other scope reaches |\n\nA write scope includes the read scope of the same resource: `tickets:write` reads tickets too, `comments:write` reads comments. `api-keys:manage` is accepted on a key but grants nothing. `GET /projects` and `GET /me` take a key with any scope."},{"title":"API Versioning & Stability","content":"The Fesk API uses URL-based versioning (`/v1/`). Current version: **v1** (stable).\n\n**Non-breaking changes** (no version bump): new optional response fields, new optional parameters, new endpoints, new enum values.\n\n**Breaking changes** require a new version and 90-day deprecation notice.\n\n**Client recommendation:** Ignore unknown JSON properties for forward compatibility."},{"title":"Rate Limiting","content":"All endpoints enforce sliding-window rate limits. A request made with an API key counts against the key's limit for its kind, whatever it calls: a `GET` against the key's reads, a `POST`, `PUT`, `PATCH` or `DELETE` against its writes.\n\n| Type | Limit | Window |\n|------|-------|--------|\n| API key, reads (GET) | 300 requests | 60 seconds |\n| API key, writes | 100 requests | 60 seconds |\n| No API key | 30 requests | 60 seconds |\n\nEndpoints with a smaller limit of their own:\n- `POST /webhooks/{id}/test`: 10 requests / 60 seconds\n\nRate limit headers are included in the responses of every counted request:\n- `X-RateLimit-Limit`, Maximum requests in current window\n- `X-RateLimit-Remaining`, Remaining requests in current window\n- `X-RateLimit-Reset`, Unix timestamp at which the oldest request counted leaves the sliding window and one more request is allowed\n\nA request refused before it is counted carries none of them: a `401`, or a `400` for a key in the query string. A `402` does carry them.\n\nWhen rate limited, you receive HTTP `429` with `Retry-After`. A `429` is not counted against the monthly API call allowance. Wait `Retry-After` seconds, or back off exponentially with jitter, and retry."},{"title":"API Call Allowance","content":"Besides the per-minute rate limit, an account has a monthly API call allowance: the free calls of the month, or, with paid API usage, the monthly call limit set in billing settings. A call beyond it is answered with `402` (`error_code` `billing_required` or `limit_reached`).\n\nThe rate limit is checked first, so a call answered with `429` is not counted. Every other response to a request made with an API key says what is left:\n- `X-Allowance-Limit`, the month's allowance or call limit\n- `X-Allowance-Remaining`, calls left before `402`, free calls granted to the account included\n- `X-Allowance-Reset`, Unix timestamp at which the month's count starts again\n- `X-Allowance-Warning`, `low` once 10% of the allowance or less is left, `exhausted` when nothing is; absent otherwise\n\nAn account with paid API usage and no call limit has no ceiling, and its responses carry none of these headers.\n\nWhen the allowance runs low, the account's admins are notified once per month. A client that does a lot of work in one go should read `X-Allowance-Remaining` and stop at a clean point."},{"title":"Optimistic Concurrency","content":"Tickets include a `version` field (integer, starting at 1) that increments on each update.\n\n**To enable conflict detection:** include `\"version\": <n>` in your PUT request body. The server checks this against the current DB version and returns `409 Conflict` if they differ.\n\n**When omitted:** the server still uses a version-based `WHERE` clause internally to prevent corrupt partial writes. In rare cases of truly simultaneous requests, you may still receive `409`. However, the common lost-update scenario (read \u2192 pause \u2192 write over someone else's changes) is NOT detected without an explicit `version` field. For multi-user integrations, always include `version`.\n\nOn 409: re-fetch the ticket, re-apply your changes, retry with the new version.\n\nNote: Only ticket PUT updates support optimistic concurrency. Comments, webhooks, and attachments do not."},{"title":"Webhook Delivery Guarantees","content":"Webhooks use **best-effort delivery with retries**. A delivery fails when the endpoint answers with a status outside 2xx or does not answer within 10 seconds. It is attempted up to 5 times: once, then again 1 minute, 5 minutes, 30 minutes and 2 hours after the attempt before it. A worker that runs once a minute sends the deliveries, so each one leaves up to a minute after it is due. After the last attempt the delivery is recorded as failed; the subscription stays active.\n\n- No automatic disabling after repeated failures\n- No replay/redelivery API, poll endpoints to reconcile missed events\n\nEvent ordering is NOT guaranteed across concurrent deliveries. Use `timestamp` and `version` for recency."},{"title":"Idempotency","content":"Every POST, `PUT /tickets/{id}` and `PATCH /tickets/{id}/status` take the `Idempotency-Key` header: 1 to 128 characters, or the request answers `400`. Other PUT, PATCH and DELETE requests ignore it.\n\nWhen the same API key retries the same method and path with the same key and payload, the API replays the stored successful (2xx) response instead of doing the work twice, for 90 days. Replayed responses include `X-Idempotency-Replay: true`.\n\nIf the same key is reused with a different payload, or while the first request is still running, the API returns `409 Conflict`."},{"title":"CORS","content":"The API answers browser requests from any origin. Send the key in the `Authorization` or the `X-API-Key` header; cookies are not used, so credentials are never needed.\n\n| Setting | Value |\n|---------|-------|\n| Allowed Origins | `*` |\n| Allowed Methods | GET, POST, PUT, PATCH, DELETE, OPTIONS |\n| Allowed Headers | Content-Type, Authorization, X-API-Key, Idempotency-Key, X-Correlation-ID |\n| Exposed Headers | X-Correlation-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Allowance-Limit, X-Allowance-Remaining, X-Allowance-Reset, X-Allowance-Warning, Idempotency-Key, X-Idempotency-Replay |\n| Preflight Cache | 3600 seconds (1 hour) |\n\nKeep a key that can write out of code served to people who should not have it: anyone who can read the page can read the key. A browser integration is best limited to a read-only key or put behind your own server."}]}