Auth
Org registration, login, token refresh, logout, and invite acceptance. See conventions for the token model this section implements.
POST /v1/orgs — register a new org
Auth: public.
Creates an org and its first (admin) user in one call — this is what adj signup does.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
org_name |
string | yes | Display name. |
slug |
string | yes | Short id used at login; unique across the deployment. |
admin_email |
string | yes | Unique within... nothing yet — this is the first user in a brand-new org. |
admin_password |
string | yes | Minimum 8 characters. |
$ curl -s https://api.stagdb.com/v1/orgs -X POST -H 'Content-Type: application/json' \
-d '{"org_name":"Acme Inc","slug":"acme","admin_email":"[email protected]","admin_password":"hunter22"}'
Response 201 Created
{
"org_id": "1c9c14da-8b0e-4c6e-9f1a-2d7c3a5b6e42",
"slug": "acme",
"admin_user_id": "7a1e9c3d-5b2f-4c8a-9d0e-2f3a4b5c6d7e"
}
You are not logged in yet — this call does not return tokens. Follow up
with POST /v1/auth/login.
| Status | When |
|---|---|
| 400 | org_name, slug, or admin_email missing, or admin_password under 8 characters ("invalid input"). |
| 409 | The slug or admin email is already taken ("conflict"). |
POST /v1/auth/login — org-scoped login
Auth: public.
Request body
| Field | Type | Required |
|---|---|---|
org_slug |
string | yes |
email |
string | yes |
password |
string | yes |
$ curl -s https://api.stagdb.com/v1/auth/login -X POST -H 'Content-Type: application/json' \
-d '{"org_slug":"acme","email":"[email protected]","password":"hunter22"}'
Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmciOiIxYzljMTRkYS04YjBlLTRjNmUtOWYxYS0yZDdjM2E1YjZlNDIiLCJyb2xlIjoiYWRtaW4ifQ.dQw4w9WgXcQ",
"refresh_token": "3n8fK2pQZmR1sV6xY0bC4dE7gH9jL_wA5oT8uI2rP0k"
}
token is the access JWT (15m default TTL); refresh_token is the opaque
rotating token (720h default TTL). See conventions.
Login timing is deliberately constant-ish whether the org, the email, or the password is wrong — the API never reveals which one failed.
| Status | When |
|---|---|
| 400 | org_slug, email, or password missing/empty ("org_slug, email, and password are required"). |
| 401 | Unknown org slug, unknown email in that org, or wrong password — all identical; wire body is the generic {"error": "unauthorized"}. |
POST /v1/auth/refresh — rotate a refresh token
Auth: public (the refresh token itself is the credential).
Request body
| Field | Type | Required |
|---|---|---|
refresh_token |
string | yes |
$ curl -s https://api.stagdb.com/v1/auth/refresh -X POST -H 'Content-Type: application/json' \
-d '{"refresh_token":"3n8fK2pQZmR1sV6xY0bC4dE7gH9jL_wA5oT8uI2rP0k"}'
Response 200 OK — same shape as login: a new token and a new
refresh_token. The presented refresh token is invalidated the instant this
call succeeds; only the new one in the response is valid going forward.
| Status | When |
|---|---|
| 400 | refresh_token missing ("refresh_token is required"), or malformed JSON. |
| 401 | Token unknown, expired, or already rotated/revoked — wire body is the generic {"error": "unauthorized"} in every case. Presenting a previously rotated token trips reuse detection: every refresh token for that user is revoked as a precaution, and this call still returns 401. |
POST /v1/auth/logout — revoke one session
Auth: public (idempotent by design — the CLI can call it even if the local access token already expired, as long as the refresh token is still known locally).
Request body
| Field | Type | Required |
|---|---|---|
refresh_token |
string | yes |
$ curl -s https://api.stagdb.com/v1/auth/logout -X POST -H 'Content-Type: application/json' \
-d '{"refresh_token":"3n8fK2pQZmR1sV6xY0bC4dE7gH9jL_wA5oT8uI2rP0k"}' -o /dev/null -w '%{http_code}\n'
204
Revokes only the session tied to that refresh token. Calling it again with
the same (now-revoked) token is a no-op, not an error — logout is idempotent.
For "sign me out everywhere," see DELETE /v1/account/sessions in
Account.
Response: 204 No Content on success and on repeat calls.
POST /v1/invites/accept — accept an invite
Auth: public (the invite token itself is the credential; this is how a brand-new user gets their first login).
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
token |
string | yes | The bare invite token (not an adj1.… code — the CLI unwraps that client-side before calling this). |
password |
string | yes | Minimum 8 characters — sets the new user's password. |
$ curl -s https://api.stagdb.com/v1/invites/accept -X POST -H 'Content-Type: application/json' \
-d '{"token":"9c2f7e51d4a8b3660f1e8d2c5a9b4370c6e1f8a25d3b7c90e4f6a1d82b5c3e70","password":"correcthorse"}'
Response 201 Created
{"user_id": "3e6f0a2b-7c8d-4e1f-9a2b-3c4d5e6f7a8b", "email": "[email protected]"}
No tokens are returned here either — follow up with POST /v1/auth/login
using the org slug embedded in the invite (the CLI reads it from the adj1.…
code so you never have to know it).
| Status | When |
|---|---|
| 400 | token empty or password under 8 characters; or the invite has expired (invite tokens have a 15-minute to 720-hour TTL set by ttl_seconds at creation — see Users). Wire body is the generic {"error": "invalid input"}. |
| 404 | Token unknown — no invite matches it ({"error": "not found"}). |
| 409 | The invite was already accepted, or a user with that email already exists in the org ({"error": "conflict"}). |