HTTP API: tasks
The 14 operations tagged tasks, each with its parameters, its body and what it answers.
List tasks
GET /v1/tasks
List tasks, narrowed by whatever the query string asks for.
Parameters
workspace_id(stringornull, in query): Which workspace, by id or slug. Needed when you can reach several.project(stringornull, in query): Restrict to one project, by key or id.status(stringornull, in query): Restrict to one status key.tag(stringornull, in query): Restrict to items carrying this tag, without the '#'. Matched however it was capitalised.status_category(stringornull, in query): Restrict to one status category: todo, in_progress, done or cancelled. Unlike 'status' this survives an installation renaming its statuses, so it is the handle a board or a completed-work view should use. Naming a finished category reaches finished work without also passing include_completed.assignee(stringornull, in query): Restrict to one assignee, by username or id. 'me' is the account you are signed in as — which is not the same as ?actor=me on the change feed, where it means this credential.claimed_by(stringornull, in query): Restrict to what one account is holding, by username or id. 'me' is the account you are signed in as. Expired claims are not held, so they are left out.type(stringornull, in query): Restrict to one item type key.parent(stringornull, in query): Restrict to the children of one task, by ref or id. Use with subtree=true for everything beneath it rather than one level.subtree(boolean, in query): With parent: include the whole subtree, not only direct children.q(stringornull, in query): Words to look for in the title or the description. Every one must appear.due_before(stringornull, in query): Due strictly before.due_after(stringornull, in query): Due strictly after.include_completed(booleanornull, in query): Include finished tasks. Left unsaid it is off, unless status_category names a finished category — asking for finished work and not mentioning completion is not a request for an empty page.deferred(string, in query): How to treat work deferred to a future date: 'include' (the default, and unchanged), 'exclude' to hide it, or 'only' to see just what is parked.deleted(boolean, in query): Show *only* what is in the trash, rather than including it. A mixed list would be the one place a caller cannot tell a live item from a deleted one.ready(boolean, in query): Only tasks that can actually be started: nothing unfinished blocks them and they are not deferred to a future date. Does not yet consider a task's own status — one marked 'blocked' by hand is still returned, because that is a declared block rather than a tracked dependency (see §5.5).to_act_on(boolean, in query): Only work that is yours to act on: assigned to you, or to nobody, or held by you. Wider than 'assignee=me', which is strictly assigned — on a shared backlog most of what anybody can pick up belongs to nobody yet. Composes with 'ready'.order(stringornull, in query): Comma-separated sort fields, '-' for descending: '-importance,due_at'.limit(integerornull, in query): How many to return. At least 1; capped at the instance's max_page_size.cursor(stringornull, in query): Continue after a previous page.include_total(boolean, in query): Count the whole result. Costs a second scan; off by default.group_by(stringornull, in query): Split the answer into groups, each with an allowance of its own, so that no group can be starved by its neighbours. 'status_category' is the one axis today. Changes the response shape: 'groups', each with 'key', 'items' and its own 'page'. Every group the axis has is present, including empty ones.group_limit(integerornull, in query): How many rows each group carries. Defaults to 25 and cannot exceed 100, because the cost of a grouped request is this times the number of groups. Only meaningful with group_by.include(stringornull, in query): Extras to return beside the items, comma-separated.linksadds alinksarray of the links among this page's items — each one{id, link_type, label, source, target}, reported once however many of its ends are on the page. Absent unless asked for, and unaffected byfields.format(stringornull, in query): 'full' (default), 'compact' for one aligned line per item, or 'ids' for the addresses alone. Compact is roughly a twentieth the size of full.fields(stringornull, in query): Comma-separated field names to return instead of the whole item, e.g. 'ref,title,due_at'. GET /v1/meta lists what each entity has. Cannot be combined with 'format'.
Responses
200:Collection_Task_. Successful Response422:HTTPValidationError. Validation Error
Create a task
POST /v1/tasks
Create a task, from structured fields or from a captured line.
Request body: Create as application/json, required.
Responses
201:Task. Successful Response422:HTTPValidationError. Validation Error
Read one task
GET /v1/tasks/{id_or_ref}
Return one task, by id or by ref.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.format(stringornull, in query): 'full' (default), 'compact' for one aligned line per item, or 'ids' for the addresses alone. Compact is roughly a twentieth the size of full.fields(stringornull, in query): Comma-separated field names to return instead of the whole item, e.g. 'ref,title,due_at'. GET /v1/meta lists what each entity has. Cannot be combined with 'format'.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Change a task
PATCH /v1/tasks/{id_or_ref}
Change a task. Omitted fields are untouched; nulls clear (docs/design.md §8.3).
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Request body: Update as application/json, required.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Move a task to the trash
DELETE /v1/tasks/{id_or_ref}
Soft-delete a task. It stays recoverable (docs/design.md §6.9).
The deleted task is returned rather than an empty 204, so a caller can see when it happened without asking again — and so an agent can tell a repeat call apart from a first one.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Take a task, so nobody else does
POST /v1/tasks/{id_or_ref}/claim
Take a lease on a task, or renew one you already hold.
A lease, not a lock (docs/design.md §14.11): it expires, and an expired one is ignored rather than needing anybody to clear it. Workers die mid-task, and a claim that outlived its holder would strand the work permanently.
Claiming something somebody else holds is a 409 naming who and until when. Claiming
something you already hold renews it, and keeps the instant you first took it.
?ready=true hides work another worker holds, and never hides your own.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.minutes(integerornull, in query): How long the lease lasts. Defaults to the instance's setting.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Mark a task finished
POST /v1/tasks/{id_or_ref}/complete
Mark a task finished, in whatever this workspace calls its finished status.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Move a task under another, or to the top level
POST /v1/tasks/{id_or_ref}/move
Re-parent a task, taking its subtask tree with it.
The endpoint §8 reserved, rather than a field on PATCH. Changing a project is a
field being wrong and the subtree following is an invariant; changing a parent can be
refused for being a cycle, which is a question about the shape of the tree and cannot be
answered from this row alone.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Request body: Move as application/json, required.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
When does this come round?
GET /v1/tasks/{id_or_ref}/occurrences
Expand a repeating task's rule into the dates it produces.
§6.7 reserved this, and the decision behind it is why it exists at all: one occurrence is real
and the rest are computed, so show me every birthday is a question about a view rather
than about the backlog. Nothing is stored and nothing is materialised — a GET that wrote
would break a read-only credential and race two concurrent readers.
It answers about the series, from whichever end the caller is holding. A person is always looking at an occurrence, because the template is in no listing; asking an occurrence when it next comes round is asking its series.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.until(stringornull, in query): Stop here. A date, an instant, or an expression like '+3 months'.limit(integer, in query): At most this many.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Occurrences. Successful Response422:HTTPValidationError. Validation Error
Give a task back
POST /v1/tasks/{id_or_ref}/release
Give a task back, so somebody else can take it.
Releasing something nobody holds is not an error and records nothing — a worker tidying up after itself should not have to check first.
Anybody who may change the task may release it, not only the holder. The case this exists for is a worker that died holding a lease, and requiring its credential would put the remedy in the hands of the one principal that cannot act.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Take a task out of the trash
POST /v1/tasks/{id_or_ref}/restore
Restore a soft-deleted task (docs/design.md §6.9).
The half that made soft delete soft, and for a long time it did not exist — §6.9
promised a deleted item was restorable, a trash_retention_days setting was declared, and
EventAction.RESTORED has always been in the vocabulary, with nothing clearing
deleted_at. That setting is gone — nothing ever purged the trash, so it was one more
place the promise was made.
Registered before the parameterised deletes below it for routing.check's reason, and
POST rather than DELETE ?restore= because it is not a deletion of anything.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
Let one occurrence of a repeat go by
POST /v1/tasks/{id_or_ref}/skip
Cancel this occurrence and bring the next one.
Cancelled rather than done, deliberately: both are finished and both advance the series, and I did not do this is a different fact about the month from I did.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace, by id or slug.
Responses
200:Task. Successful Response422:HTTPValidationError. Validation Error
What has been checked against this task
GET /v1/tasks/{id_or_ref}/verifications
Return what has been checked against this task, newest first.
Self-reported evidence is a record, not a proof. An agent can post exit code 0 without running anything. What this is worth is being a durable, attributable, invalidatable record of what was checked — never "verified work", and nothing reading it should say so.
Each record carries the tree it ran against, where there was one. Whether a record has expired is a comparison against the tree you are standing on, and this instance cannot make it: it has no checkout. A record with no tree hash cannot expire at all, which is a different answer from being current.
Newest first, unlike a comment thread: a record is not read as a story, and what a caller wants is the most recent thing that was checked.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace.
Responses
200:Collection_Verification_. Successful Response422:HTTPValidationError. Validation Error
Record what was checked against this task
POST /v1/tasks/{id_or_ref}/verifications
Record what was checked against this task.
Self-reported evidence is a record, not a proof. An agent can post exit code 0 without running anything. What this is worth is being a durable, attributable, invalidatable record of what was checked — never "verified work".
tree_hash is what makes it invalidatable, and git rev-parse HEAD^{tree} prints one. Send
it where there is one and leave it out where there is not: a record without one is still a
record, it simply cannot expire.
A failing record is worth keeping and is the more useful half of the pair. This was tried and did not work is what stops it being tried again.
Parameters
id_or_ref(string, in path, required): The item's ref — a plain integer, as returned inref— or its id.42and019f…are both accepted. Write#42in prose, never in a URL.workspace_id(stringornull, in query): Which workspace.
Request body: RecordVerification as application/json, required.
Responses
201:Verification. Successful Response422:HTTPValidationError. Validation Error