API reference contents
API reference
Documentation
The work resource
A work item is one thing on the desk. Its shape is the shape the screen reads, including the action computed for it — so an agent and a person are looking at the same row.
The shape of a work item
Every row of GET /work and the work object of
GET /work/{id} have this shape. Nothing in it is an enum the caller has to
guess at except kind, and kind is explained below.
{
"id": 47,
"title": "Renew the business licence",
"detail": null,
"kind": "renewal",
"status": "open",
"due_date": "2027-02-28",
"days_to_due": 138,
"overdue": false,
"priority": 92,
"waiting_on": null,
"days_waiting": null,
"last_error": null,
"action": { "type": "approve", "label": "Approve and send", "hint": "A message is ready." },
"permission": "reach_out",
"allowed": false,
"has_draft": true
}
Identity and description
| Field | Type | Meaning |
|---|---|---|
id | integer | The row. Stable for the life of the item, and the only identifier any endpoint takes. |
title | string | What the item is, in the desk's words. Written by the desk when the work was proposed. |
detail | string or null | The desk's own note about the row. Null on most rows. A completion note is appended here. |
kind | string | The desk's word for this sort of work. Not a fixed list. See below. |
kind is vocabulary, not an enum. It is whatever word this
particular business uses for that sort of work — renewal,
filing, inspection, milestone,
chase, and others. It is safe to filter on a value that came back from
GET /work and unsafe to hard-code a list, because a different desk has
different kinds. GET /desk tells you which kinds a given desk actually
uses; see The desk.
State and calendar
| Field | Type | Meaning |
|---|---|---|
status | string | One of open, in_progress, waiting, done, closed. |
due_date | date or null | YYYY-MM-DD. Null when the work has no date attached. |
days_to_due | integer or null | Days from today. Negative when overdue. |
overdue | boolean | True when a due date exists and has passed. |
priority | integer 1–100 | Higher is more urgent. This is what the desk sorted by, not a judgement for a client to re-derive. |
Waiting
| Field | Type | Meaning |
|---|---|---|
waiting_on | string or null | Who or what the item is stopped on, named as a person would name it. |
days_waiting | integer or null | How long it has been out. Null when nothing is being waited on. |
last_error | string or null | The last thing that went wrong. When this is set the row is a fault rather than a decision, and the action becomes fix. |
What the row is asking for
action is the one thing this row needs next, and of whom. It is null when
nothing is needed — a row that has closed, or one that is merely waiting.
"action": { "type": "approve", "label": "Approve and send", "hint": "A message is ready." }
| Key | Meaning |
|---|---|
type | Which of the eight states this row is in. Machine-readable. |
label | What the person sees on the button. Show this rather than writing a label of your own. It is the same word the screen uses, so a person moving between the two is not translating. |
hint | The sentence under the button, saying why. Written for a person to read. Worth passing to an agent as context: it carries the reason, and sometimes the name of whoever is being waited on. |
The eight action types
The set is deliberately small, and each value is a state the data can actually be in. An action invented for a condition the desk cannot detect would be a button that does nothing, so there are no others.
| Type | Label | What it means |
|---|---|---|
approve |
Approve and send | A message is prepared and sitting in the row. The only thing between the desk and the work being done is a person's approval. Nothing has gone out. Read the draft, then complete the row. |
run |
Let the desk do it / Run it now | Nobody is needed for this one. Either the work is automatic and not yet started, or the desk has already been granted permission for this class and is going to do it on its own schedule. The two labels are the same type in two situations: the first row that has never been attempted, and the second where a draft exists and the button is only there to act now rather than wait. |
nudge |
Chase it | The desk is waiting on somebody outside and they have gone quiet — long enough that asking again is the work. This is the only action that exists because of a wait rather than in spite of one. |
wait |
Waiting / Waiting 12d | Something has gone out and an answer is owed. Nothing on this side can move it. A row with this action has no button on the screen, deliberately — a button here would be an invitation to invent work. Do not retry it. |
own |
Assign to me | The law, or another organisation, puts this step in a person's hands and only there: a wet signature, a physical inspection, something sworn before a witness. The desk prepares it completely and stops. The hint says whether the desk will wait on somebody else once the person's part is done. |
answer |
How does this one get done? | The desk genuinely does not know something only a person knows — usually who this goes to. There is nobody on the row to write to, so there is nothing to draft. A fact is missing, not a decision. |
prepare |
Prepare this | Nothing is drafted yet, and there is somebody reachable to draft it for. Call POST /work/{id}/draft to write it. The action then becomes approve. |
fix |
Needs a look | The desk was allowed to do this one, tried, and could not. The row is not a decision any more, it is a fault, and its hint carries the failure. last_error is set on the same row. An exception outranks everything else: a row with a last_error reports fix whatever else is true of it. |
Two consequences worth building around. First, action can be null, and a
client that assumes otherwise will break on the first closed row it sees. Second,
deferring — snoozing — is available on every row and is not one of the eight,
because it is never the thing the row is about.
permission and allowed
These two fields describe what the desk may do without asking, and they are the mechanism behind the promise that nothing sends itself.
"permission": "reach_out",
"allowed": false
| Field | Meaning |
|---|---|
permission |
Which of the desk's permission classes covers this row, or null when nothing does. The six classes are reach_out, chase, file, commit, money and sign. Null always asks — work the desk does not recognise is never delegated. |
allowed |
Whether the owner has already granted the desk that class. true means the desk may act on this row without asking. false means it will ask first. |
Three things follow, and they matter more than the field list. money and
sign cannot be granted at all: the desk prepares those and a person performs
them, so a row carrying either always has allowed: false. An unclassified row
always has permission: null and allowed: false. And
allowed is not a promise that the act will succeed — it is a statement
about permission, which is why last_error can be set on a row that was
allowed.
allowed on a row is the exact gate behind
POST /work/{id}/run. That endpoint answers 403 not_permitted
when allowed is false, so a client can check the field and never
make the call. The full picture of what is granted and what each grant would cover is at
GET /permissions; see The desk.
Why has_draft exists
has_draft is a boolean saying a message is prepared and waiting. It looks
redundant next to action.type === "approve", and it is not — it is the
difference between a list that can be read and a list that has to be opened row by row.
A queue of forty rows is a screen somebody scans, not a set of records somebody reads in
full. Knowing which rows already have something written means the screen can show the
number of drafts waiting without fetching forty items, and a client can do the same. It
also explains a row whose action is run while a draft exists: the
message is written, the class is granted, and the desk is simply going to send it on its
own schedule.
One precise detail, because it is the kind of thing that quietly disagrees between two
surfaces: has_draft is true only when a message is still in the
drafted state. A message that has already gone out is in
messages[] and does not make has_draft true. The flag and the
panel mean the same thing.
Messages and timeline
Only GET /work/{id} returns these. They are omitted from the list endpoint
because a queue of forty rows does not need forty message bodies.
| Field | Meaning |
|---|---|
channel | How the message travels: email, and whatever else this desk uses. |
direction | outbound or inbound. |
subject, body | The message itself, so a client can show what is about to go out before approving it. |
status | drafted (written, nothing sent), approved (signed off but not delivered — no mailbox, or the send was refused), sent (it left). |
actor | Who wrote or approved it. desk when the desk drafted it; otherwise the name passed to the write, or api. |
created_at, sent_at, approved_at | ISO 8601 UTC. sent_at and approved_at are null until they happen. |
timeline is the same history as sentences, oldest first, each entry carrying
kind, at and text. It is the fastest way to answer
"what has actually happened to this row" without decoding statuses, and it is safe to show
verbatim.
Ordering, and honesty about it
GET /work is ordered the way the screen orders it: overdue first, then by
priority, then by how close the due date is. That ordering is a presentation decision
rather than a contract. It is not a stable sort, it is not a guaranteed tie-break, and a
client should not build an index on the assumption that two consecutive pages preserve a
global order. If a specific order matters, sort in the client.
There is one more honest limit. The queue behind this endpoint is read to a fixed depth
before limit and cursor are applied. A desk with an
exceptionally large open queue may see the tail of it truncated rather than paged. The
detail is in Pagination.
Related
- Actions — what can be done to a row, and what each write returns.
- Pagination — paging the queue without skipping rows.
- The desk — which kinds and blocks this desk actually uses.