API Endpoints
All paths are relative to `/v1`. All request/response bodies are JSON. Examples use `devray@example.com` / `Pass@2026` as placeholder credentials.
All paths are relative to /v1. All request/response bodies are JSON. Examples use devray@example.com / Pass@2026 as placeholder credentials.
POST /signup
Public. Creates a new user.
Request:
{ "email": "devray@example.com", "password": "Pass@2026" }Response 201:
{ "data": { "user_id": "019f...", "email": "devray@example.com" } }Possible errors: bad_request, user_exists (409), rate_limited (429)
POST /login
Public. Authenticates and returns a token pair.
Request:
{ "email": "devray@example.com", "password": "Pass@2026" }Response 200:
{ "data": { "access_token": "eyJ...", "refresh_token": "a1b2..." } }Possible errors: invalid_credentials (401), account_locked (403), rate_limited (429)
POST /refresh
Public. Rotates a refresh token for a new pair.
Request:
{ "refresh_token": "a1b2..." }Response 200: same shape as /login.
Possible errors: invalid_token (401), token_reused (401) — see authentication.md for what token_reused means and how a client should respond to it.
POST /logout
Requires auth. Revokes one session.
Request:
{ "session_id": "019f..." }Response 200:
{ "data": { "status": "logged out" } }Possible errors: session_not_owned (403), not_found (404)
POST /logout-all
Requires auth. Revokes every session for the authenticated user. No request body.
Response 200:
{ "data": { "status": "logged out of all devices" } }GET /verify
Requires auth. Confirms the presented access token is valid.
Response 200:
{ "data": { "user_id": "019f..." } }GET /sessions
Requires auth. Lists every active session for the authenticated user.
Response 200:
{
"data": [
{ "id": "019f...", "ip": "1.2.3.4", "user_agent": "Mozilla/5.0...", "created_at": "2026-08-14T08:32:38Z" }
]
}Note: the session's refresh token hash is deliberately never included in this response — see design-decisions.md.
DELETE /sessions/{id}
Requires auth. Revokes a specific session.
Response 200:
{ "data": { "status": "session revoked" } }Possible errors: session_not_owned (403) — attempting to revoke a session that does not belong to the authenticated user.
POST /change-password
Requires auth. Changes the authenticated user's password.
Request:
{ "current_password": "Pass@2026", "new_password": "NewPass@2027" }Response 200:
{ "data": { "status": "password changed, please log in again" } }Important: this revokes every session for the user, including the one making this request. The client must discard its tokens and prompt for a fresh login.
Possible errors: invalid_credentials (401) — wrong current password.
POST /delete-account
Requires auth. Permanently deletes the authenticated user's account.
Request:
{ "current_password": "Pass@2026" }Response 200:
{ "data": { "status": "account deleted" } }This is irreversible. All sessions, and the user's own data (subject to ON DELETE CASCADE foreign keys in the schema), are removed.
POST /email/request-change
Requires auth. Starts an email change — sends a verification token to the new address. The email is not changed yet.
Request:
{ "new_email": "proguy@example.com" }Response 200:
{ "data": { "status": "verification email sent" } }Possible errors: user_exists (409) — the new address already belongs to another account.
POST /email/confirm-change
Public — deliberately requires no Authorization header, since the user may be clicking a link from an email client with no active session. The token itself is the proof of authorization.
Request:
{ "token": "<raw token from the verification email>" }Response 200:
{ "data": { "status": "email changed" } }Possible errors: verification_token_invalid (400) — invalid or already used. verification_token_expired (400).
GET /health
Public. Confirms the server can reach its database.
Response 200:
{ "data": { "status": "ok" } }Response 503 if the database is unreachable.