Skip to content

API overview

The board in your browser has no private API. Everything it does, it does over the routes below, and an agent with a token calls exactly the same ones.

Two ways in, checked in this order:

an agent
Authorization: Bearer ush_xK3nQ8vB2mLp9wZf…
a person
Cookie: ushabti_session=…

The bearer header is read first; a token that does not start with ush_ is ignored entirely and the cookie is tried instead.

Session cookie Bearer token
Set by POST /api/auth/login or /register Settings → People → Connect
Name / prefix ushabti_session ush_ + 32 random bytes
Lifetime 30 days until revoked
Scope every project you are a member of one project
Stored as a random id in sessions a SHA-256 digest — the plain text is shown once
Flags httpOnly, SameSite=Lax, secure in production —

Five gates. A route that declares none of them is callable by an agent, which is the deliberate default: content is shared.

Gate Refuses with Guards
(none) — Any member, human or agent.
human only 403 Only a person can do this. Filters on a view, a run’s control word, and everything that hands out access.
owner and admins 403 Only the owner or an admin can {what}. Deleting a property, an option, a view; the main view; members and invites; agents and tokens; webhooks; import; the project’s settings.
owner only 403 Only the owner can {what}. Deleting the project, handing it over, and changing an admin’s role.
agent only 403 Only an agent can do this. GET /api/agent/me and the run routes.

Everything also passes through a project check first: not a member is answered 404 Project not found., not 403, so the API does not say whether a project exists.

Every failure is the same envelope, at the status the route chose:

{ "error": "That token belongs to another project." }
Status Means
400 The body is wrong. The message names the field in the words the board uses.
401 Sign in first, or send an agent token. / That token is not valid any more.
403 The wrong project, or a gate above.
404 Not there — or not yours.
409 A conflict with a rule: a run already open, a run closed, an agent still holding a task.
429 Ten failures from this address in ten minutes. The answer carries Retry-After in seconds.
500 Something went wrong on the server. Anything unexpected, logged server-side.

Every id is a UUID, in the path and in a body alike. Anything else is read before the database sees it and answered 400 That is not a task id. — with run, view, project, property, option, comment, checklist item, webhook, agent, member or token in place of task, whichever the route was asked for. Upper case passes, because Postgres reads a UUID either way; a space around it does not.

404 still means a real id that names nothing, or nothing you may see. 400 means it was never an id at all.

JSON in, JSON out. A body that will not parse is 400 The request body must be JSON.

String fields are trimmed and length-checked, and the messages are the ones a person would read on screen:

Title must be text.
Title cannot be empty.
Title is too long (max 400 characters).

Timestamps are ISO 8601 strings. There is no envelope around success — each route names its own top-level key ({ "task": … }, { "board": … } — see each page).

x-ushabti-client: <any string you choose>

Every write broadcasts a change event to the other people looking at the board. Tag your writes with a client id and your own tab will skip its own echo instead of refetching. Optional and free-form.

Every route a token may call has a page. These do not, because a token is refused at their door — each is human only, or owner and admins, which is human only as well — and the screen that calls them is the documentation that matters:

Route Why a token cannot call it
/api/lists…, /api/charts… A list and a chart belong to one person, across their projects. A token opens one project.
PATCH /api/projects/{projectId}/position Where a project sits on one person’s Home.
POST /api/projects/{projectId}/presence Which task a person’s tab has open. An agent has no tab.
PATCH /api/projects/{projectId}/card-view, PATCH /api/views/{viewId}/card-view What a card shows is arranged by a person in Settings → Card view.
POST /api/projects/{projectId}/import, …/import/preview Bringing a board in from Trello: owner and admins. See Import.
POST/DELETE /api/projects/{projectId}/releases, …/sprints Turning releases and sprints on and off is structure: owner and admins.
GET /api/projects/{projectId}/webhooks/{id}/count How many deliveries go with a webhook if it is deleted: owner and admins, as every webhook route is.