xtract/API documentationOpenAPI JSON

The route catalogue below is generated from OpenAPI. Protected admin endpoints require configured Supabase Auth and database access.

All API routes #

Every operation registered with OpenAPI appears here automatically. Documentation pages and static assets are not application API operations. Full request and response schemas are available in OpenAPI JSON.

MethodPathPurposeAuthentication
GET/admin/roastersList, filter, and sort roastersBearer token; endpoint permissions apply
POST/admin/roastersCreate a roasterBearer token; endpoint permissions apply
GET/admin/roasters/summaryCount active and inactive roastersBearer token; endpoint permissions apply
GET/admin/roasters/{id}Get a roaster for editingBearer token; endpoint permissions apply
PATCH/admin/roasters/{id}Update a roasterBearer token; endpoint permissions apply
GET/admin/coffees/tasting-notesFind tasting notes for the coffee formBearer token; endpoint permissions apply
GET/admin/coffeesList, filter, and sort coffeesBearer token; endpoint permissions apply
POST/admin/coffeesCreate a coffeeBearer token; endpoint permissions apply
GET/admin/coffees/summaryCount active and inactive coffeesBearer token; endpoint permissions apply
GET/admin/coffees/{id}Get a coffee for editingBearer token; endpoint permissions apply
PATCH/admin/coffees/{id}Update a coffeeBearer token; endpoint permissions apply
GET/admin/users/brandsFind active brands for scoped user accessBearer token; endpoint permissions apply
GET/admin/usersSearch, filter, and sort users or suspended accounts (50 per page)Bearer token; endpoint permissions apply
GET/admin/users/summaryTotal, active (not suspended), and suspended account counts; excludes deleted usersBearer token; endpoint permissions apply
POST/admin/users/inviteInvite a new Supabase user by email; requires an MFA-authenticated admin sessionBearer token; endpoint permissions apply
GET/admin/users/{id}Read a user's profile and current permissionsBearer token; endpoint permissions apply
PATCH/admin/users/{id}Update profile and Supabase login email; requires recent password and MFABearer token; endpoint permissions apply
DELETE/admin/users/{id}Delete a suspended Supabase user and their owned Storage objectsBearer token; endpoint permissions apply
PATCH/admin/users/{id}/permissionsAssign platform admin, roaster and brand memberships; revoke older sessionsBearer token; endpoint permissions apply
POST/admin/users/{id}/accessSuspend or restore a Supabase account; restoration requires recent authenticationBearer token; endpoint permissions apply
POST/admin/users/{id}/mfa/resetRemove a user's MFA factors and revoke existing sessions; requires recent password and MFABearer token; endpoint permissions apply
POST/admin/users/welcome-emailSend Supabase password setup/reset emails to up to 10 selected usersBearer token; endpoint permissions apply
GET/admin/rolesSearch, filter, and sort platform roles and assignment counts (50 per page)Bearer token; endpoint permissions apply
POST/admin/rolesCreate a role with no permissions; requires recent password and MFABearer token; endpoint permissions apply
GET/admin/roles/{id}Read a platform roleBearer token; endpoint permissions apply
PATCH/admin/roles/{id}Edit role name and description; reserved system role names cannot changeBearer token; endpoint permissions apply
GET/admin/accessCheck staff access before MFABearer token; endpoint permissions apply
GET/admin/sessionVerify a complete admin sessionBearer token; endpoint permissions apply
GET/portal/{portal}/accessCheck business membership before optional MFABearer token; endpoint permissions apply
GET/portal/{portal}/sessionVerify business portal sessionBearer token; endpoint permissions apply
GET/portal/{portal}/roasters/{roasterId}/coffeesList your roaster coffeesBearer token; endpoint permissions apply
POST/portal/{portal}/roasters/{roasterId}/coffeesAdd a coffee to your roasterBearer token; endpoint permissions apply
GET/portal/{portal}/roasters/{roasterId}/coffees/{id}Read your coffee for editingBearer token; endpoint permissions apply
PATCH/portal/{portal}/roasters/{roasterId}/coffees/{id}Edit your roaster coffeeBearer token; endpoint permissions apply
GET/mobile/discoverRead the active public discovery cataloguePublic
GET/mobile/catalogue/{kind}/lettersCount active listings by first letterPublic
GET/mobile/catalogue/{kind}Browse 50 active listings per pagePublic
GET/mobile/catalogue/{kind}/{id}Read a public coffee or roasterPublic
GET/mobile/profileRead your application profileBearer token; endpoint permissions apply
PATCH/mobile/profileComplete onboarding or edit your own profileBearer token; endpoint permissions apply
GET/mobile/profiles/{id}Read your own or another completed public profileBearer token; endpoint permissions apply
POST/mobile/profile/avatarReplace your own profile pictureBearer token; endpoint permissions apply
GET/healthCheck API livenessPublic
GET /admin/roasters — List, filter, and sort roasters

Returns up to 50 roasters, searchable by name, slug, or contact email, filterable by status, and sorted by an allowlisted field. Requires current platform-admin membership and MFA.

200
A page of roasters and pagination metadata
401
Unauthenticated
403
Not an MFA-verified platform administrator
429
Admin request limit exceeded; retry after 60 seconds
503
Access verification is unavailable
POST /admin/roasters — Create a roaster

Creates a roaster and attributes the write to the authenticated platform administrator.

201
Created roaster
400
Invalid roaster details
401
Unauthenticated
403
Not an MFA-verified platform administrator
409
A roaster already uses this slug
429
Admin request limit exceeded; retry after 60 seconds
503
Roaster creation failed
GET /admin/roasters/summary — Count active and inactive roasters

200
Roaster totals by status
401
Unauthenticated
403
Not an MFA-verified platform administrator
429
Admin request limit exceeded; retry after 60 seconds
503
Access verification is unavailable
GET /admin/roasters/{id} — Get a roaster for editing

200
Roaster details
400
Invalid roaster ID
401
Unauthenticated
403
Not an MFA-verified platform administrator
404
Roaster not found
429
Admin request limit exceeded; retry after 60 seconds
503
Roaster lookup failed
PATCH /admin/roasters/{id} — Update a roaster

Updates the editable roaster fields and records the authenticated administrator in the audit trigger.

200
Updated roaster
400
Invalid roaster ID or details
401
Unauthenticated
403
Not an MFA-verified platform administrator
404
Roaster not found
409
A roaster already uses this slug
429
Admin request limit exceeded; retry after 60 seconds
503
Roaster update failed
GET /admin/coffees/tasting-notes — Find tasting notes for the coffee form

Searches canonical names and reviewed synonyms; returns up to 50 canonical choices. Requires current platform-admin membership and MFA.

200
Canonical tasting-note choices
400
Invalid search
401
Unauthenticated
403
Not an MFA-verified platform administrator
429
Admin request limit exceeded
503
Tasting-note search unavailable
GET /admin/coffees — List, filter, and sort coffees

Returns up to 50 coffees, searchable by coffee name, slug, origin, or roaster name, filterable by status, and sorted by an allowlisted field. Requires current platform-admin membership and MFA.

200
A page of coffees and pagination metadata
401
Unauthenticated
403
Not an MFA-verified platform administrator
429
Admin request limit exceeded; retry after 60 seconds
503
Access verification is unavailable
POST /admin/coffees — Create a coffee

Creates a coffee and attributes the write to the authenticated platform administrator.

201
Created coffee
400
Invalid coffee details
401
Unauthenticated
403
Not an MFA-verified platform administrator
409
This roaster already has a coffee with this slug
429
Admin request limit exceeded; retry after 60 seconds
503
Coffee creation failed
GET /admin/coffees/summary — Count active and inactive coffees

200
Coffee totals by status
401
Unauthenticated
403
Not an MFA-verified platform administrator
429
Admin request limit exceeded; retry after 60 seconds
503
Access verification is unavailable
GET /admin/coffees/{id} — Get a coffee for editing

200
Coffee details
400
Invalid coffee ID
401
Unauthenticated
403
Not an MFA-verified platform administrator
404
Coffee not found
429
Admin request limit exceeded; retry after 60 seconds
503
Coffee lookup failed
PATCH /admin/coffees/{id} — Update a coffee

Updates the editable coffee fields and records the authenticated administrator in the audit trigger.

200
Updated coffee
400
Invalid coffee ID or details
401
Unauthenticated
403
Not an MFA-verified platform administrator
404
Coffee not found
409
This roaster already has a coffee with this slug
429
Admin request limit exceeded; retry after 60 seconds
503
Coffee update failed
GET /admin/users/brands — Find active brands for scoped user access

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/users — Search, filter, and sort users or suspended accounts (50 per page)

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/users/summary — Total, active (not suspended), and suspended account counts; excludes deleted users

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
POST /admin/users/invite — Invite a new Supabase user by email; requires an MFA-authenticated admin session

201
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/users/{id} — Read a user's profile and current permissions

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
PATCH /admin/users/{id} — Update profile and Supabase login email; requires recent password and MFA

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
DELETE /admin/users/{id} — Delete a suspended Supabase user and their owned Storage objects

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
PATCH /admin/users/{id}/permissions — Assign platform admin, roaster and brand memberships; revoke older sessions

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
POST /admin/users/{id}/access — Suspend or restore a Supabase account; restoration requires recent authentication

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Recent authentication required
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
POST /admin/users/{id}/mfa/reset — Remove a user's MFA factors and revoke existing sessions; requires recent password and MFA

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
POST /admin/users/welcome-email — Send Supabase password setup/reset emails to up to 10 selected users

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/roles — Search, filter, and sort platform roles and assignment counts (50 per page)

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
POST /admin/roles — Create a role with no permissions; requires recent password and MFA

201
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/roles/{id} — Read a platform role

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
PATCH /admin/roles/{id} — Edit role name and description; reserved system role names cannot change

200
Successful operation
400
Invalid request, denied access, conflict, rate limit, or unavailable dependency
401
Invalid request, denied access, conflict, rate limit, or unavailable dependency
403
Invalid request, denied access, conflict, rate limit, or unavailable dependency
404
Invalid request, denied access, conflict, rate limit, or unavailable dependency
409
Invalid request, denied access, conflict, rate limit, or unavailable dependency
413
Invalid request, denied access, conflict, rate limit, or unavailable dependency
429
Invalid request, denied access, conflict, rate limit, or unavailable dependency
503
Invalid request, denied access, conflict, rate limit, or unavailable dependency
GET /admin/access — Check staff access before MFA

Requires a verified token, current platform-admin membership and a live session. Allows AAL1 for MFA setup and returns only the caller's user ID and MFA status.

200
Current admin access status
401
Request denied or access verification unavailable
403
Request denied or access verification unavailable
413
Request denied or access verification unavailable
429
Request denied or access verification unavailable
503
Request denied or access verification unavailable
GET /admin/session — Verify a complete admin session

Requires current platform-admin membership and MFA (AAL2). Rejects revoked, expired, banned or deleted-user sessions. Admin sessions have an eight-hour absolute limit.

200
Current admin access status
401
Request denied or access verification unavailable
403
Request denied or access verification unavailable
413
Request denied or access verification unavailable
429
Request denied or access verification unavailable
503
Request denied or access verification unavailable
GET /portal/{portal}/access — Check business membership before optional MFA

Requires a verified Supabase token, live confirmed account and active scoped membership. Sessions expire after eight hours; suspension, revocation and inactive memberships deny access. The session endpoint also requires AAL2 when the account has a verified MFA factor. Returns at most 100 businesses belonging to the caller. Platform admin and community roles alone do not grant portal access.

200
Current portal access
401
Access denied or verification unavailable
403
Access denied or verification unavailable
429
Access denied or verification unavailable
503
Access denied or verification unavailable
GET /portal/{portal}/session — Verify business portal session

Requires a verified Supabase token, live confirmed account and active scoped membership. Sessions expire after eight hours; suspension, revocation and inactive memberships deny access. The session endpoint also requires AAL2 when the account has a verified MFA factor. Returns at most 100 businesses belonging to the caller. Platform admin and community roles alone do not grant portal access.

200
Current portal access
401
Access denied or verification unavailable
403
Access denied or verification unavailable
429
Access denied or verification unavailable
503
Access denied or verification unavailable
GET /portal/{portal}/roasters/{roasterId}/coffees — List your roaster coffees

Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.

200
Coffee records, 50 per page
400
Request rejected or service unavailable
401
Request rejected or service unavailable
403
Request rejected or service unavailable
429
Request rejected or service unavailable
503
Request rejected or service unavailable
POST /portal/{portal}/roasters/{roasterId}/coffees — Add a coffee to your roaster

Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.

201
Coffee created
400
Request rejected or service unavailable
401
Request rejected or service unavailable
403
Request rejected or service unavailable
409
Request rejected or service unavailable
413
Request rejected or service unavailable
429
Request rejected or service unavailable
503
Request rejected or service unavailable
GET /portal/{portal}/roasters/{roasterId}/coffees/{id} — Read your coffee for editing

Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.

200
Coffee record
400
Request rejected or service unavailable
401
Request rejected or service unavailable
403
Request rejected or service unavailable
404
Request rejected or service unavailable
429
Request rejected or service unavailable
503
Request rejected or service unavailable
PATCH /portal/{portal}/roasters/{roasterId}/coffees/{id} — Edit your roaster coffee

Requires a live roaster owner/editor session, account MFA when configured, and active membership in this roaster. Records are scoped in SQL. Writes preserve ownership and slug and are attributed to the verified actor.

200
Coffee saved
400
Request rejected or service unavailable
401
Request rejected or service unavailable
403
Request rejected or service unavailable
404
Request rejected or service unavailable
409
Request rejected or service unavailable
413
Request rejected or service unavailable
429
Request rejected or service unavailable
503
Request rejected or service unavailable
GET /mobile/profile — Read your application profile

200
Visible profile; never includes email, roles or account state
400
Request denied or unavailable
401
Request denied or unavailable
403
Request denied or unavailable
404
Request denied or unavailable
409
Request denied or unavailable
413
Request denied or unavailable
429
Request denied or unavailable
503
Request denied or unavailable
PATCH /mobile/profile — Complete onboarding or edit your own profile

Own-profile writes only. Usernames are normalized and case-insensitively unique. Profile creation grants only the existing Community role. Private/incomplete profiles are invisible to other mobile users. Verified live sessions required, including AAL2 when MFA is enrolled.

200
Visible profile; never includes email, roles or account state
400
Request denied or unavailable
401
Request denied or unavailable
403
Request denied or unavailable
404
Request denied or unavailable
409
Request denied or unavailable
413
Request denied or unavailable
429
Request denied or unavailable
503
Request denied or unavailable
GET /mobile/profiles/{id} — Read your own or another completed public profile

200
Visible profile; never includes email, roles or account state
400
Request denied or unavailable
401
Request denied or unavailable
403
Request denied or unavailable
404
Request denied or unavailable
409
Request denied or unavailable
413
Request denied or unavailable
429
Request denied or unavailable
503
Request denied or unavailable
POST /mobile/profile/avatar — Replace your own profile picture

Private storage. API checks image bytes, re-encodes to 512px JPEG without metadata, and deletes the replaced object. Pictures are included only in authorized, uncached profile responses. No direct Storage grants or permanent public URLs.

200
Visible profile; never includes email, roles or account state
400
Request denied or unavailable
401
Request denied or unavailable
403
Request denied or unavailable
404
Request denied or unavailable
409
Request denied or unavailable
413
Request denied or unavailable
429
Request denied or unavailable
503
Request denied or unavailable

Health check #

GET/health

Check whether the API is responding. This checks the HTTP service only; it does not test the database or other dependencies.

Authentication
None
Parameters
None

Response fields 200 OK

statusstring
Always "ok" when the service responds.
servicestring
The service name: "xtract-api".

Returns JSON with Cache-Control: no-store.

Request
curl --fail http://localhost:3003/health
No authentication required
Response200 · example
{
  "status": "ok",
  "service": "xtract-api"
}

Send a request to check this API host.

Errors #

Unknown routes return 404 with a JSON error. Check the request path and HTTP method.

Admin routes return { "error": "CODE" }: 401 for invalid credentials, 403 for denied access, 413 for oversized requests, and 503 when access verification is unavailable. A 503 must never grant access.

Response404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Route not found."
  }
}

API or Supabase? #

Use Hono for application data and business operations. Use Supabase directly for authentication and explicitly approved features. When in doubt, use the API.

OperationUseRule
Sign-in, sign-out and session managementSupabase AuthUse the client SDK with a publishable key.
Application data reads and writesHono APIKeep validation, permissions and business rules on the server.
Payments, invitations, roles, email and pushHono APIThese operations need server-side authorization, secrets or side effects.
Realtime and StorageSupabase, with access policiesApprove access for the feature first. Use API-issued signed URLs where appropriate.
Direct table queries or RPCs from a clientDocumented exceptionReview schema exposure, grants and RLS. Test unauthorized and cross-organization access.
Schema changes and database maintenanceMigrations / serverVersion SQL migrations here. Keep privileged operations out of clients.

For example, sign in with supabase.auth.signInWithPassword(). Change a team member’s role through an authorized Hono endpoint, rather than updating the table from the client.

Requirements for direct Supabase access

Document the feature, allowed operations and reason for direct access. Test unauthorized users and cross-organization access. Secret and service-role keys must stay on the server.

Current database configuration

The local Supabase Data API exposes public and graphql_public. Application tables live in core, access, msg and audit; helpers live in private. These application schemas are not exposed. RLS policies alone do not make tables reachable through the SDK.

Server-side SDK access also requires deliberate schema exposure and grants, even with a service-role key. Alternatively, use a server-side Postgres connection. Keep private internal. Privileged credentials bypass RLS, so Hono must enforce user and organization permissions explicitly.

Supabase API security ↗ · API keys ↗

Authentication #

Use Supabase Auth for sign-in and session management. Configure the client with your Supabase project URL and publishable key.

For protected API routes, send the user’s access token in the Authorization header:

Bearer <access_token>

The API verifies the token and checks current platform-admin membership and a live session in the existing database tables. /admin/access allows password-only staff sessions for MFA setup; /admin/session requires MFA. /health is public.

User and role writes also require password and TOTP authentication within five minutes. User lists return 50 accounts per page; welcome-email requests accept up to 10 recipients. Suspension and deletion are linked to Supabase Auth. Permission changes and restoration require a fresh user sign-in.

All authenticated admin routes share a per-account limit of 240 requests per minute and 30 writes per minute across API instances. Existing account-operation limits still apply. Exceeding a limit returns 429 with Retry-After: 60; unavailable counters fail closed with 503.

Account operations can return 409 for conflicts and 429 for rate limits. The API returns per-recipient outcomes for welcome emails; retry only failed recipients.

Admin browser origins are restricted by ADMIN_ORIGIN. The admin app currently calls Hono from its Next.js server. CORS never replaces authorization.

Supabase sign-in example
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  SUPABASE_URL,
  SUPABASE_PUBLISHABLE_KEY,
)

const { data, error } = await supabase.auth.signInWithPassword({
  email,
  password,
})
if (error) throw error

// For protected API routes (admin/session also requires MFA):
// Authorization: Bearer <data.session.access_token>

Requires @supabase/supabase-js, a configured project and a user. Supply your own configuration and form values.

Local development #

Run these commands from the api directory. The server uses port 3003 by default; set PORT to change it.

The documentation and health endpoint run without a Supabase connection.

Terminal
npm ci
npm run dev