API requests, responses, and operations
Public API operations
Request IDs and errors
Every response includes X-Request-Id. Every error repeats that exact value in
the JSON body, so a client can report one identifier to support:
{ "error": { "code": "REVISION_CONFLICT", "message": "The Work Item changed. Refresh it and retry with its current ETag.", "details": {"currentRevision": 5} }, "requestId": "..."}Every response also includes Cache-Control: no-store. Once a request has
passed PAT authentication, successful and error responses use the same
correlation and rate-limit headers:
X-Request-Id, RateLimit-Limit, RateLimit-Remaining, and
RateLimit-Reset. A rate-limit rejection adds Retry-After; no
X-RateLimit-* compatibility aliases are emitted. JSON errors always have
the shape {error: {code, message, fieldErrors?, details?}, requestId}.
Error code values are stable machine fields; clients must not parse the
English message. The v1 contract includes AUTH_REQUIRED, INVALID_TOKEN,
INSUFFICIENT_SCOPE, FORBIDDEN, NOT_FOUND, METHOD_NOT_ALLOWED,
ORGANIZATION_SUSPENDED, VALIDATION_ERROR, INVALID_CURSOR, CONFLICT,
PRECONDITION_REQUIRED, REVISION_CONFLICT, IDEMPOTENCY_KEY_REQUIRED,
IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_REQUEST_IN_PROGRESS,
WORK_ITEM_STATUS_REQUIRES_TRANSITION, INVALID_STATUS_TRANSITION,
INVALID_SPRINT_TRANSITION, ACTIVE_SPRINT_EXISTS,
SPRINT_HAS_UNFINISHED_WORK_ITEMS, INVALID_COMPLETION_TARGET,
LINK_DUPLICATE, LINK_CONTRADICTION, WORK_ITEM_ARCHIVED,
WORK_ITEM_HAS_CHILDREN, PROJECT_ARCHIVED, RATE_LIMITED,
PAYLOAD_TOO_LARGE, and INTERNAL_ERROR.
NOT_FOUND is also used to hide resources outside the requested Organization,
Project, PAT grant, or current membership. Errors do not contain stack traces,
SQL, tokens, or internal storage fields.
The stable status mapping is:
| Status | Codes |
|---|---|
| 400 | VALIDATION_ERROR, INVALID_CURSOR, IDEMPOTENCY_KEY_REQUIRED, INVALID_COMPLETION_TARGET |
| 401 | AUTH_REQUIRED, INVALID_TOKEN |
| 403 | INSUFFICIENT_SCOPE, FORBIDDEN, ORGANIZATION_SUSPENDED |
| 404 | NOT_FOUND |
| 405 | METHOD_NOT_ALLOWED |
| 409 | CONFLICT, WORK_ITEM_STATUS_REQUIRES_TRANSITION, INVALID_STATUS_TRANSITION, INVALID_SPRINT_TRANSITION, ACTIVE_SPRINT_EXISTS, SPRINT_HAS_UNFINISHED_WORK_ITEMS, LINK_DUPLICATE, LINK_CONTRADICTION, WORK_ITEM_ARCHIVED, WORK_ITEM_HAS_CHILDREN, PROJECT_ARCHIVED, IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_REQUEST_IN_PROGRESS |
| 412 | REVISION_CONFLICT |
| 413 | PAYLOAD_TOO_LARGE |
| 428 | PRECONDITION_REQUIRED |
| 429 | RATE_LIMITED |
| 500 | INTERNAL_ERROR |
Unknown v1 paths return 404 NOT_FOUND. Unsupported methods return
405 METHOD_NOT_ALLOWED with Allow; both use the same JSON error envelope.
HEAD follows the corresponding GET authentication rules without a body.
OPTIONS returns 204 with Allow, X-Request-Id, and
Cache-Control: no-store, without a body; this does not enable cross-origin
access.
Cursor pagination
Organizations and Projects are ordered by name ASC, id ASC. Sprints are
ordered by undated-last, then startDate ASC, id ASC. Work Items are ordered
by updatedAt DESC, workItemKey DESC. Comments are ordered by
createdAt ASC, id ASC.
Collections return:
{ "items": [], "page": {"limit": 50, "hasMore": false, "nextCursor": null}}The default limit is 50. Organization, Project, Sprint, label, and member lists
cap the limit at 100; comment and activity lists cap it at 200. Work Item and
profile Work collections cap the limit at 200. Cursors are opaque: store the exact
nextCursor string and send it back unchanged. Never decode, construct, or
modify one. An invalid cursor returns 400 INVALID_CURSOR. Cursors encode a
position only for ordinary collections: the requested filters are reapplied on
every page. Work Item cursors additionally bind the normalized filters, sort,
complete ordering tuple, and asOf; changing those values returns
400 INVALID_CURSOR. For ordinary collections, clients should keep the
original query, including any collection filter, while walking a cursor; the
server reapplies the current filters and the cursor itself does not carry a
filter fingerprint. Lists do not provide a frozen snapshot, so normal updates
between page requests can move Work Items across page boundaries.
Query parameters are strict. Unknown parameters and duplicate scalar
parameters are rejected. Repeatable filters accept at most 100 values each.
Work Item collections support overdue, exclusive dueBefore/dueAfter,
sort=updated|dueDate|priority|rank, archived, and sparse fields=.
id, key, and revision are always returned; an empty fields=
means the complete projection. Cursors bind filters, sort, ordering tuple, and
one captured asOf timestamp, and are invalid with different filters.
The Work Item ordering tuples are exact and stable:
updated:updatedAt DESC, workItemKey DESC, id ASC;dueDate: non-null due dates first, thendueDate ASC, updatedAt DESC, workItemKey DESC, id ASC;priority:urgent, high, medium, low, thenupdatedAt DESC, workItemKey DESC, id ASC;rank: overdue first (most overdue first), then status bucketin_progress/in_review,backlog/todo,done, priority, due date with nulls last,updatedAt DESC, workItemKey DESC, id ASC.
The first page captures asOf; subsequent pages use the cursor’s value for
overdue and rank comparisons. Keyset pagination is not snapshot isolation:
deletions cannot create duplicates, while newly inserted or moved rows may be
omitted or appear according to their current position after the cursor.
Idempotency
New POST, PATCH, and DELETE mutations require an
Idempotency-Key matching [A-Za-z0-9._:-]{8,128}. Generate a new key for
every logical mutation and reuse that same key only when retrying that same
request. Records expire after 24 hours. This requirement applies to new v1
mutations introduced by the work order; the original 20 operations retain
their existing header requirements.
The server hashes the method, resolved resource identity, normalized request
body (including defaults), and normalized If-Match or bulk concurrency mode.
A completed retry returns the original status, body, and ETag, with
Idempotency-Replayed: true; a replay of 204 No Content has no body and no
Content-Type. Replays do not repeat domain effects, activity, or API audit.
Reusing a key for a different request returns 409 IDEMPOTENCY_KEY_REUSED. A
currently processing request returns 409 IDEMPOTENCY_REQUEST_IN_PROGRESS and
a short Retry-After value. The claim, domain mutation, audit, and completion
record commit atomically.
Bulk Work Item requests contain 1–50 operations and return HTTP 200 with per-item statuses. Each item runs in a savepoint, so expected validation or domain failures do not roll back earlier successful items; unexpected failures roll back the whole request. Bulk successes write their normal audit/activity effects, while a replay writes none.
Bulk paths are:
POST /api/v1/organizations/{organizationSlug}/bulk-work-items;PATCH /api/v1/organizations/{organizationSlug}/bulk-work-items;POST /api/v1/organizations/{organizationSlug}/bulk-work-item-transitions.
Create operations contain projectKey plus the normal Work Item create
fields. Update operations contain projectKey, workItemKey, changes, and
optionally revision; transition operations contain projectKey,
workItemKey, targetStatus, and optionally revision. The default mode is
require-revision, where every operation must provide a positive revision.
last-write-wins requires every operation to omit revision. A valid envelope
returns {results: [...]} in input order even when every item fails.
Optimistic concurrency
Work Item detail reads and successful Work Item mutations include an ETag such
as "wi-4", derived from the integer revision. For PATCH and transitions,
send the ETag in If-Match. A successful write increments the revision and
returns the new ETag.
- missing
If-Match→428 PRECONDITION_REQUIRED; - stale exact ETag →
412 REVISION_CONFLICT; If-Match: *→ explicit last-write-wins override.
The only valid exact forms are quoted positive revision ETags such as
"wi-4" and "sprint-4". Weak ETags, unquoted values, zero/negative revisions, and
comma-separated ETag lists are rejected.
Clients should read, preserve the ETag, and retry a stale write only after
deciding how to merge the latest state. The normal CLI path should not use the
wildcard. Sprints use "sprint-<revision>" and Projects use
"project-<revision>". New mutation replays of 204 No Content contain
no JSON body or Content-Type.
The Work Item ETag also covers embedded labels, so label attach/detach uses the same compare-and-set rule. Sprint transitions and Project update/archive/ unarchive mutations use their resource-specific ETag. Exact tags are quoted; weak, unquoted, zero/negative, and comma-listed forms are invalid.
Rate limits
Authenticated public routes use the shared rate-limit implementation, with process-local
counters by default. These counters are not shared across application replicas;
RATE_LIMIT_STORE_MAX_KEYS only bounds local memory. Deployment-wide enforcement
requires an upstream limiter or an implemented shared backend. See the
the public authentication guide for credential requirements. A rejected request is
429 RATE_LIMITED with Retry-After, X-Request-Id, and the standard rate
limit headers. Clients should back off and retry the same idempotency key for a
retryable mutation rather than generating a second mutation.
All Public API responses use Cache-Control: no-store. JSON mutation bodies
are limited to 1 MiB based on the streamed body size, including when a client
omits or lies about Content-Length; oversized bodies return
413 PAYLOAD_TOO_LARGE. Deprecation and Sunset headers are wired through
typed operation metadata; no v1 operation is currently marked deprecated.
Complete operation inventory
The authoritative registry contains the following 51 exact method/path/
operation-ID entries. The required scope column shows the minimum PAT scope;
membership, role, Organization suspension, and resource-grant checks still
apply. getOpenApi is the only public operation without a PAT; all other
entries require PAT authentication. Full request and response schemas remain
authoritative in GET /api/v1/openapi.json.
| Method | Path | Operation ID | Scope |
|---|---|---|---|
| GET | /api/v1/me |
getMe |
— |
| GET | /api/v1/openapi.json |
getOpenApi |
— |
| GET | /api/v1/users/{publicId} |
getUserProfile |
profile:read |
| GET | /api/v1/users/{publicId}/work |
listUserProfileWork |
profile:read |
| GET | /api/v1/users/{publicId}/activity |
listUserProfileActivity |
profile:read |
| GET | /api/v1/users/{publicId}/avatar |
getUserProfileAvatar |
profile:read |
| GET | /api/v1/organizations |
listOrganizations |
organization:read |
| GET | /api/v1/organizations/{organizationSlug} |
getOrganization |
organization:read |
| GET | /api/v1/organizations/{organizationSlug}/users |
listOrganizationUsers |
organization:members:read |
| GET | /api/v1/organizations/{organizationSlug}/work-items |
listOrganizationWorkItems |
work_item:read |
| GET | /api/v1/my/work |
listMyWork |
work_item:read |
| POST | /api/v1/organizations/{organizationSlug}/bulk-work-items |
bulkCreateWorkItems |
work_item:write |
| PATCH | /api/v1/organizations/{organizationSlug}/bulk-work-items |
bulkUpdateWorkItems |
work_item:write |
| POST | /api/v1/organizations/{organizationSlug}/bulk-work-item-transitions |
bulkTransitionWorkItems |
work_item:write |
| GET | /api/v1/organizations/{organizationSlug}/projects |
listProjects |
project:read |
| POST | /api/v1/organizations/{organizationSlug}/projects |
createProject |
project:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey} |
getProject |
project:read |
| PATCH | /api/v1/organizations/{organizationSlug}/projects/{projectKey} |
updateProject |
project:write |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/archive |
archiveProject |
project:write |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/unarchive |
unarchiveProject |
project:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/sprints |
listProjectSprints |
project:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/sprints |
createProjectSprint |
project:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/sprints/{sprintId} |
getProjectSprint |
project:read |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/sprints/{sprintId}/transitions |
listProjectSprintTransitions |
project:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/sprints/{sprintId}/transitions |
transitionProjectSprint |
project:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/labels |
listProjectLabels |
project:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/labels |
createProjectLabel |
project:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items |
listWorkItems |
work_item:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items |
createWorkItem |
work_item:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey} |
getWorkItem |
work_item:read |
| PATCH | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey} |
updateWorkItem |
work_item:write |
| DELETE | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey} |
deleteWorkItem |
work_item:delete |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/archive |
archiveWorkItem |
work_item:delete |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/unarchive |
unarchiveWorkItem |
work_item:delete |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/transitions |
listWorkItemTransitions |
work_item:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/transitions |
transitionWorkItem |
work_item:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/links |
listWorkItemLinks |
work_item:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/links |
createWorkItemLink |
work_item:write |
| DELETE | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/links/{linkId} |
deleteWorkItemLink |
work_item:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/activity |
listWorkItemActivity |
work_item:read |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/activity |
listProjectActivity |
work_item:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/labels |
createWorkItemLabelAssignment |
work_item:write |
| DELETE | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/labels/{labelId} |
deleteWorkItemLabelAssignment |
work_item:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/comments |
listWorkItemComments |
comment:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/comments |
createWorkItemComment |
comment:write |
| DELETE | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/comments/{commentId} |
deleteWorkItemComment |
comment:delete |
| PATCH | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/comments/{commentId} |
updateWorkItemComment |
comment:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/attachments |
listWorkItemAttachments |
attachment:read |
| POST | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/attachments |
createWorkItemAttachment |
attachment:write |
| GET | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/attachments/{attachmentId} |
downloadWorkItemAttachment |
attachment:read |
| DELETE | /api/v1/organizations/{organizationSlug}/projects/{projectKey}/work-items/{workItemKey}/attachments/{attachmentId} |
deleteWorkItemAttachment |
attachment:delete |
Framework-generated HEAD, OPTIONS, 404, and 405 behavior is not part of
this registry; it remains inside the public API method response wrapper.
