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"}).

Next