Home › Documentation › API › Work

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

FieldTypeMeaning
idintegerThe row. Stable for the life of the item, and the only identifier any endpoint takes.
titlestringWhat the item is, in the desk's words. Written by the desk when the work was proposed.
detailstring or nullThe desk's own note about the row. Null on most rows. A completion note is appended here.
kindstringThe 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

FieldTypeMeaning
statusstringOne of open, in_progress, waiting, done, closed.
due_datedate or nullYYYY-MM-DD. Null when the work has no date attached.
days_to_dueinteger or nullDays from today. Negative when overdue.
overduebooleanTrue when a due date exists and has passed.
priorityinteger 1–100Higher is more urgent. This is what the desk sorted by, not a judgement for a client to re-derive.

Waiting

FieldTypeMeaning
waiting_onstring or nullWho or what the item is stopped on, named as a person would name it.
days_waitinginteger or nullHow long it has been out. Null when nothing is being waited on.
last_errorstring or nullThe 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." }
KeyMeaning
typeWhich of the eight states this row is in. Machine-readable.
labelWhat 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.
hintThe 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.

TypeLabelWhat 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
FieldMeaning
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.

FieldMeaning
channelHow the message travels: email, and whatever else this desk uses.
directionoutbound or inbound.
subject, bodyThe message itself, so a client can show what is about to go out before approving it.
statusdrafted (written, nothing sent), approved (signed off but not delivered — no mailbox, or the send was refused), sent (it left).
actorWho wrote or approved it. desk when the desk drafted it; otherwise the name passed to the write, or api.
created_at, sent_at, approved_atISO 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.

  • 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.