Skip to content

Authentication API

These routes are for people. A bearer token is ignored here — an agent has no account to manage and no password to change.


Creates an account and signs it straight in.

Auth — none, but gated by USHABTI_SIGNUP.

request
{ "email": "ada@example.com", "name": "Ada", "password": "at-least-eight" }
Field Rules
email required, ≤ 200 characters, must look like an address
name required, ≤ 80 characters
password required, at least 8 characters — not trimmed, no maximum

Your avatar colour is not yours to choose here: it is derived from the email, so two people are unlikely to collide. Change it later at /account.

200 — and a Set-Cookie
{ "user": { "id": "…", "email": "ada@example.com", "name": "Ada", "color": "#3fb0c8", "emoji": null } }
Status Message
403 This board is not taking new accounts. Ask the owner to add you. — checked before the body is even read
400 That email address does not look correct. · The password must have at least 8 characters.
409 An account with that email already exists.
429 Too many tries. Wait N minutes and try again. — after ten refusals from this address

request
{ "email": "ada@example.com", "password": "…" }
200 — and a Set-Cookie
{ "user": { "id": "…", "email": "ada@example.com", "name": "Ada", "color": "#3fb0c8", "emoji": null } }

Deletes the session row and clears the cookie. No body, no auth needed, 200 {"ok": true}. Calling it without a cookie is a no-op that still succeeds.


Changes your display name, avatar colour and face. Every field is optional; send any of them.

request
{ "name": "Ada L.", "color": "#3fb0c8", "emoji": "🦊" }

color must be one of the eleven avatar colours. They are the palette that options use, except the grey #8b8f98, which reads as “nobody”:

#e0574d #d1913a #4f8a5b #3fb0c8 #4b8fbe #6d5bd0
#c2557a #b6763f #2f9e7a #7a8a2f #3d7fc1

emoji is the face drawn on that colour in place of your initials. It must be exactly one emoji: a ZWJ sequence such as 👩‍💻, a skin tone such as 👋🏽 and a flag such as 🇺🇦 are each one. null clears it, and the initials come back. The Account page offers a grid of about thirty, but the route takes any single emoji. Every member, comment author, activity actor and run agent on the board carries emoji beside color; it is null for somebody who wears their initials.

200
{ "user": { "id": "…", "name": "Ada L.", "color": "#3fb0c8", "emoji": "🦊", "email": "ada@example.com" } }

Errors: 400 Pick one of the colours on offer. for any other colour, the grey included; 400 A face is one emoji, or null for your initials. for letters, two emoji, an empty string or anything that is not a string; plus the usual length messages for Name and Colour.


Changes your password and signs every other session out.

request
{ "current": "…", "next": "at-least-eight" }
200
{ "ok": true }
Status Message
400 Send the password you use now and the one you want. · The new password must have at least 8 characters.
403 That is not the password you use now. — note 403, not 401

Sets a password with a reset link — one the owner of a project made, copied to you or emailed when the server has mail set up, or one you asked for with POST /api/auth/forgot — and signs this browser in. This is the way back into an account whose password nobody knows — see A forgotten password.

Auth — none. The token in the body is the whole of it.

request
{ "token": "ushr_…", "password": "at-least-eight" }
Field Rules
token the token out of /reset/<token>, made by POST …/members/{userId}/reset or emailed by POST /api/auth/forgot
password required, at least 8 characters

It works once and for 24 hours, and a newer link for the same person puts the older ones out of use. Setting the password ends every session of that account, then opens one for the browser that called.

200 — and a Set-Cookie
{ "ok": true }
Status Message
400 The password must have at least 8 characters.
400 This link does not work any more. Ask the owner of your project for a new one. — used, expired, superseded or never real, all four the same. While forgot is on, it ends Ask for a new one with Forgot password? on the sign-in page.
429 Too many tries. Wait N minutes and try again. — after ten dead links from this address

Emails a reset link to the account that uses this email. It is what Forgot password? on the sign-in page calls.

Auth — none. It is on only when the server has mail set up and USHABTI_URL is set.

request
{ "email": "ada@example.com" }
200 — for every email
{
"ok": true,
"message": "If an account uses that email, a link is on its way. It works once, for 24 hours."
}

The answer is the same whether or not an account uses the email, and it goes out before the server looks the email up, so the time to answer says nothing either. Then, for a person with a password, the server makes a link and emails it. An agent, an unknown email and a person with no password get no link and no email. The link is built from USHABTI_URL and never from Host or x-forwarded-host, which anybody can forge. It works once, for 24 hours, and replaces any older link for that person, exactly as one an owner makes.

Status Message
400 Type the email of your account. — the body has no email
400 Email is too long (max 200 characters).
404 This board cannot email you a link. Ask an admin of your project to make you one. — mail or USHABTI_URL is not set
429 Too many tries. Wait N minutes and try again. — after ten requests from this address, or for this email, lowercased, in ten minutes

200
{ "others": 2 }

Unexpired sessions for your account, not counting the one asking.

Ends every session except this one. 200 {"ok": true}.


GET /api/agent/me agent only

Section titled “GET /api/agent/me ”

The agent counterpart. It exists so that nothing else needs the project id in configuration.

200
{
"agent": { "id": "…", "name": "Builder", "color": "#3fb0c8", "emoji": null },
"project": { "id": "…", "name": "Ushabti roadmap", "key": "USH", "role": "member", "waiting": 2 }
}

waiting is how many live tasks of the project have an open run that waits for a person — the same number as N waiting in the top bar. An archived or deleted task does not count.

A signed-in person calling it gets 403 Only an agent can do this.