API reference
The HTTP API for Xtract applications.
http://localhost:3003The 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.
| Method | Path | Purpose | Authentication |
|---|---|---|---|
| GET | /admin/roasters | List, filter, and sort roasters | Bearer token; endpoint permissions apply |
| POST | /admin/roasters | Create a roaster | Bearer token; endpoint permissions apply |
| GET | /admin/roasters/summary | Count active and inactive roasters | Bearer token; endpoint permissions apply |
| GET | /admin/roasters/{id} | Get a roaster for editing | Bearer token; endpoint permissions apply |
| PATCH | /admin/roasters/{id} | Update a roaster | Bearer token; endpoint permissions apply |
| GET | /admin/coffees/tasting-notes | Find tasting notes for the coffee form | Bearer token; endpoint permissions apply |
| GET | /admin/coffees | List, filter, and sort coffees | Bearer token; endpoint permissions apply |
| POST | /admin/coffees | Create a coffee | Bearer token; endpoint permissions apply |
| GET | /admin/coffees/summary | Count active and inactive coffees | Bearer token; endpoint permissions apply |
| GET | /admin/coffees/{id} | Get a coffee for editing | Bearer token; endpoint permissions apply |
| PATCH | /admin/coffees/{id} | Update a coffee | Bearer token; endpoint permissions apply |
| GET | /admin/users/brands | Find active brands for scoped user access | Bearer token; endpoint permissions apply |
| GET | /admin/users | Search, filter, and sort users or suspended accounts (50 per page) | Bearer token; endpoint permissions apply |
| GET | /admin/users/summary | Total, active (not suspended), and suspended account counts; excludes deleted users | Bearer token; endpoint permissions apply |
| POST | /admin/users/invite | Invite a new Supabase user by email; requires an MFA-authenticated admin session | Bearer token; endpoint permissions apply |
| GET | /admin/users/{id} | Read a user's profile and current permissions | Bearer token; endpoint permissions apply |
| PATCH | /admin/users/{id} | Update profile and Supabase login email; requires recent password and MFA | Bearer token; endpoint permissions apply |
| DELETE | /admin/users/{id} | Delete a suspended Supabase user and their owned Storage objects | Bearer token; endpoint permissions apply |
| PATCH | /admin/users/{id}/permissions | Assign platform admin, roaster and brand memberships; revoke older sessions | Bearer token; endpoint permissions apply |
| POST | /admin/users/{id}/access | Suspend or restore a Supabase account; restoration requires recent authentication | Bearer token; endpoint permissions apply |
| POST | /admin/users/{id}/mfa/reset | Remove a user's MFA factors and revoke existing sessions; requires recent password and MFA | Bearer token; endpoint permissions apply |
| POST | /admin/users/welcome-email | Send Supabase password setup/reset emails to up to 10 selected users | Bearer token; endpoint permissions apply |
| GET | /admin/roles | Search, filter, and sort platform roles and assignment counts (50 per page) | Bearer token; endpoint permissions apply |
| POST | /admin/roles | Create a role with no permissions; requires recent password and MFA | Bearer token; endpoint permissions apply |
| GET | /admin/roles/{id} | Read a platform role | Bearer token; endpoint permissions apply |
| PATCH | /admin/roles/{id} | Edit role name and description; reserved system role names cannot change | Bearer token; endpoint permissions apply |
| GET | /admin/access | Check staff access before MFA | Bearer token; endpoint permissions apply |
| GET | /admin/session | Verify a complete admin session | Bearer token; endpoint permissions apply |
| GET | /portal/{portal}/access | Check business membership before optional MFA | Bearer token; endpoint permissions apply |
| GET | /portal/{portal}/session | Verify business portal session | Bearer token; endpoint permissions apply |
| GET | /portal/{portal}/roasters/{roasterId}/coffees | List your roaster coffees | Bearer token; endpoint permissions apply |
| POST | /portal/{portal}/roasters/{roasterId}/coffees | Add a coffee to your roaster | Bearer token; endpoint permissions apply |
| GET | /portal/{portal}/roasters/{roasterId}/coffees/{id} | Read your coffee for editing | Bearer token; endpoint permissions apply |
| PATCH | /portal/{portal}/roasters/{roasterId}/coffees/{id} | Edit your roaster coffee | Bearer token; endpoint permissions apply |
| GET | /mobile/discover | Read the active public discovery catalogue | Public |
| GET | /mobile/catalogue/{kind}/letters | Count active listings by first letter | Public |
| GET | /mobile/catalogue/{kind} | Browse 50 active listings per page | Public |
| GET | /mobile/catalogue/{kind}/{id} | Read a public coffee or roaster | Public |
| GET | /mobile/profile | Read your application profile | Bearer token; endpoint permissions apply |
| PATCH | /mobile/profile | Complete onboarding or edit your own profile | Bearer token; endpoint permissions apply |
| GET | /mobile/profiles/{id} | Read your own or another completed public profile | Bearer token; endpoint permissions apply |
| POST | /mobile/profile/avatar | Replace your own profile picture | Bearer token; endpoint permissions apply |
| GET | /health | Check API liveness | Public |
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 #
/healthCheck 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.
curl --fail http://localhost:3003/health{
"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.
{
"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.
| Operation | Use | Rule |
|---|---|---|
| Sign-in, sign-out and session management | Supabase Auth | Use the client SDK with a publishable key. |
| Application data reads and writes | Hono API | Keep validation, permissions and business rules on the server. |
| Payments, invitations, roles, email and push | Hono API | These operations need server-side authorization, secrets or side effects. |
| Realtime and Storage | Supabase, with access policies | Approve access for the feature first. Use API-issued signed URLs where appropriate. |
| Direct table queries or RPCs from a client | Documented exception | Review schema exposure, grants and RLS. Test unauthorized and cross-organization access. |
| Schema changes and database maintenance | Migrations / server | Version 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.
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.
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.
npm ci
npm run dev