Skip to content

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, then dueDate ASC, updatedAt DESC, workItemKey DESC, id ASC;
  • priority: urgent, high, medium, low, then updatedAt DESC, workItemKey DESC, id ASC;
  • rank: overdue first (most overdue first), then status bucket in_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-Match428 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.