Docs › Reference

For everyone

The invite and sign-up screens

The API calls behind the invite and sign-up screens.

Part of the HTTP API reference.

Three signed-out screens besides sign-in, each reached by its address and answered before anything else is fetched. The signed-out router (followSignedOut) stands down the moment a session exists, "so it never competes with the application's own router."

Address Draw function
#/invite/<token> drawInvite(token)
#/signup drawSignUp
#/signup/<token> drawSignupConfirm(token)

Open. "Is this link still good, and whose is it? Reachable without a session."

The token is matched by its hash, never by itself.

Refusals

Condition Status Message
unknown token 404 "That link is not one this repository gave out. Ask whoever invited you for another."
already used 410 "That link has already been used. Ask for another if you need to set a new password."
expired 410 "That link has expired. Ask whoever invited you for another."

Response: {"username": "r.iyer", "display_name": "Rohan Iyer"}.

POST /api/invites/{token}/accept — set a password and sign in

Open.

Body: {"password": "three words you remember"} — at least 10 characters.

Refusals

Condition Status Message
under 10 characters 400 "A password needs at least ten characters. Three words you would not forget beats something clever you would."
used, expired, unknown, or the account is inactive 410 "That link is no longer good. Ask whoever invited you for another."

All four conditions share one sentence here, where the GET told three of them apart — the row is locked FOR UPDATE and re-checked, so this is the authoritative answer.

Side effects: the password hash is set, failed_count cleared, the invite marked used, the superadmin flag cleared, every existing session for that account revoked ("If somebody else was in their account, this is the moment they are put out"), and a new session created.

Response: the _me shape, plus the session cookie.

GET /api/signup — is sign-up open

Open. "Reachable without a session; says nothing more."

Response: {"open": false, "why": "Sign-up is not open here. Ask whoever runs this repository to invite you.", "hours": 24}. why is null when open, and is always that one sentence when closed — never the administrator's reason.

POST /api/signup — ask for an account

Open. Status 202 on success.

The rule this is built around — read it before touching anything here:

Sign-up must never tell anybody whether an address already has an account.

The sign-in endpoint already refuses to… A sign-up form is a far better oracle than a sign-in form — type an address, and "you already have an account" answers the question outright. So it does not say that. It says the same thing for a new address and a known one — "check your email" — and it is the EMAIL that differs: a new address gets a link to confirm, a known one gets "you already have an account here, sign in". The person at that inbox learns which. Nobody else does.

"The same thing" includes how long it takes. The password is hashed before anything is looked up, on every request, so a known address — which has no password to hash — does not come back fifty milliseconds sooner than a new one and give the game away by the clock.

Body

Field Type Notes
name string whitespace collapsed; 1 to 120 characters
email string stripped, lower-cased; matched by EMAIL_RE; at most 254 characters
password string 10 to 1024 characters; may not equal the email address

Refusals that may be answered plainly — "they are about the form, not about who else is here":

Condition Status Message
no name 400 "Your name, please — it is how you are shown to everybody else here."
name over 120 400 "That name is longer than 120 characters."
a bad email 400 "'<email>' is not an email address."
under 10 characters 400 "A password needs at least ten characters. …"
over 1024 400 "That password is longer than this will take."
the password is the email 400 "Your password cannot be your email address."

Refusals about the repository

Condition Status Message
sign-up closed, for any of the five reasons 403 the SIGNUP_CLOSED sentence, never the real reason
the domain is not allowed 403 "Addresses at <domain> cannot sign up here. Ask whoever runs this repository to invite you." — names only the domain they typed, never the list
the mail could not be sent 503 "The confirmation email could not be sent just now. Try again in a few minutes."

Throttling: one mail per address per SIGNUP_RESEND_MINUTES = 10, "whichever kind it would be" — and the throttled request is answered exactly as a send would be, because "a different answer here would be a way to ask 'did somebody just try this address', which is the question the whole section refuses."

A second signup for one address replaces the first by expiring the earlier pending row, "so there is only ever one live way in per address, and it is the latest."

If the mail fails, the pending row is rolled back with it — "a link nobody was sent is a row that can only ever expire" — and the failure is recorded on a second connection so the log survives the rollback.

Response — identical in every success case, including the throttled one:

{"sent_to": "x@acme.com", "hours": 24}

SIGNUP_HOURS = 24.

Until that link is followed there is no app_user row, so POST /api/session with the right credentials cannot sign the person in. It does not answer the usual vague 401 either: it looks the pending sign-up up, checks the password against it, and on a match answers 403 naming the address and telling them to follow the link — or, past the 24 hours, telling them to sign up again. See POST /api/session in Signing in.

Open. The path must have exactly three slashes for the middleware to let it through.

Refusals (_signup_row)

Condition Status Message
unknown 404 "That link is not one this repository sent. Sign up again to get a new one."
already used 410 "That link has already been used. Your account is ready — sign in."
expired 410 "That link has expired — they last 24 hours, and a newer sign-up puts an older link out. Sign up again."

Response: {"email": "x@acme.com", "display_name": "X"}.

POST /api/signup/{token} — follow the link

Open. No body.

Docstring: "Follow the link: the account is made, given its role, and signed in."

The rules are checked AGAIN here, not only when the mail went out:

If an administrator closed sign-up, narrowed the domains or changed the role in the meantime, a link sent yesterday must not carry yesterday's rules into today.

Refusals

Condition Status Message
unknown / used / expired 404 / 410 as the GET
sign-up closed, or the domain no longer allowed 403 "Sign-up has been closed since this link was sent. Ask whoever runs this repository to invite you."
an account for that address already exists 409 "There is already an account for this address. Sign in with it." — and the pending row is marked confirmed so the link cannot be retried

The 409 names it plainly, and that is deliberate: "Holding the link proves the inbox, so saying the account exists is safe here in a way it was not on the form."

Side effects: an app_user row with username = email, note = "Made through sign-up.", the configured role over the whole repository with the note "Given on sign-up. Change it on People and Persona.", a signin logbook line, and a session.

Response: the _me shape, plus the session cookie.