Conventions
The response envelope, identifiers, timestamps and filtering — the things every endpoint shares.
The response envelope
Every response is wrapped. There is one shape for success and one for failure,
so a client can branch on success before it knows anything else.
{
"success": true,
"message": "Success",
"data": { },
"pagination": { "count": 214, "limit": 50, "offset": 0 },
"meta": { }
}{
"success": false,
"message": "You do not have the required scope for this endpoint.",
"error": {
"code": "INSUFFICIENT_SCOPE",
"details": { "required": "tasks:write" }
}
}pagination appears only on list endpoints; meta only where an endpoint has
payload-shaped extras that are neither the data nor the pagination — the grouped
task list's bucket summary, for instance. Both are omitted entirely when empty
rather than sent as null.
error.code is stable and safe to branch on. error.message is written for
humans and may be reworded at any time — never match on it. Treat an
unrecognised code as a generic failure of its HTTP status.
Identifiers
Krrim uses two kinds, and which one an endpoint takes is not arbitrary:
| Kind | Looks like | Used by |
|---|---|---|
| Task key | krrim-0042 | /tasks/{task_key}/ |
| Project key | APOLLO | /projects/{project_key}/tasks/ |
| Numeric id | 128 | everything else — comments, attachments, sprints, statuses |
Keys are the identifiers humans see and paste, so the endpoints humans reach for take keys. A task key is stable for the life of the task and survives being moved between milestones and sprints.
Timestamps
All timestamps are ISO 8601 with an explicit UTC offset:
2026-08-25T09:14:03.117000ZEncode the + in a query-string offset. A literal + in a query string
decodes to a space, so ?since=2026-08-25T00:00:00+00:00 parses as a
malformed value and the filter is skipped — you get everything back while
believing you asked for a window. Use %2B, or send Z.
Filtering
List endpoints share a common vocabulary where the field exists on the resource:
| Parameter | Example | Notes |
|---|---|---|
status | ?status=in_progress | Repeatable for an OR |
assignee | ?assignee=14 | Repeatable |
priority | ?priority=high | |
label | ?label=regression | |
milestone, sprint | ?sprint=7 | |
blocked | ?blocked=true | Matches the is_blocked badge exactly |
search | ?search=timeout | Full-text over title and description |
ordering | ?ordering=-updated_at | Prefix - for descending |
Unknown query parameters are ignored rather than rejected, so adding one to a future version does not break a client that sends it early.
Partial updates
PATCH applies only the fields present in the body. PUT replaces the resource
and is subject to the same validation as a create — omitting a field on a PUT
is a request to clear it, which is rarely what an integration means. Prefer
PATCH where the endpoint offers it.
Concurrent edits
Endpoints that support optimistic concurrency accept a version with the write
and answer STALE_WRITE if the record moved on in the meantime. The error
carries the current state so the client has something to merge against, rather
than only being told it lost.
Rate-limit headers
Present on every token-authenticated response, not just the 429:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1756100000See Rate limits.