Authentication
Every CRTX API endpoint requires a valid Supabase-issued JWT in the
Authorization header. There are no anonymous endpoints.
Authorization: Bearer <access_token>
#How it works today
CRTX delegates identity to Supabase Auth. The flow:
- A user signs in (email/password) through Supabase — in the frontend this is the browser Supabase client; programmatically it's the Supabase Auth API.
- Supabase returns an access token (a JWT signed with ES256) and a refresh token.
- Your client sends the access token as a Bearer token on every API call.
- The backend validates it against Supabase's JWKS endpoint:
- fetches the signing key for the token (cached in-process),
- verifies the
ES256signature, - requires
audience = "authenticated", - enforces expiry.
- On success, the decoded claims are available to the endpoint;
subis the user id used for all ownership and access checks.
Invalid or missing tokens return 401 (Not authenticated, Token expired, or
Invalid token). No access to a resource returns 403.
Relevant config: SUPABASE_JWKS_URL (the well-known JWKS URL for your project).
The Next.js frontend additionally enforces auth at the edge via middleware —
unauthenticated browser requests are redirected to /login.
#Getting a token programmatically
For scripts and server-to-server use, sign in via the Supabase Auth REST API to obtain an access token, then call CRTX with it.
# 1. Sign in to Supabase to get an access_token
TOKEN=$(curl -s -X POST \
"$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com", "password": "…" }' \
| jq -r .access_token)
# 2. Call CRTX
curl "$BASE_URL/collections/" -H "Authorization: Bearer $TOKEN"
# Python, using the Supabase client
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_ANON_KEY)
session = sb.auth.sign_in_with_password({"email": EMAIL, "password": PASSWORD})
token = session.session.access_token
import httpx
r = httpx.get(f"{BASE_URL}/collections/",
headers={"Authorization": f"Bearer {token}"})
Access tokens are short-lived; refresh them with the Supabase refresh token (the
client SDKs do this automatically). Build retry-on-401 + refresh into any
long-running integration.
#Admin claims
Some operations are gated to admins via a custom claim. The retrieval-config
endpoints (GET/PUT /collections/{id}/config) require app_metadata.is_admin
to be truthy on the user. Set this claim in Supabase (via the Admin API / a
service-role update to the user's app_metadata) for operator accounts.
#Limitations of the current model (and what's next)
The JWT model is designed for interactive users. For headless automation, CI pipelines, embedding CRTX into another product, or giving customers scoped access, a per-user JWT is awkward — it expires, it represents a human, and it carries that human's full access.
The recommended answer is a dedicated API-key layer that sits alongside JWT auth. That design — hashed keys, scopes, per-collection restriction, rate limiting, webhooks, and an embeddable widget — is written up in Programmatic access & API keys.
See the full endpoint contract in the API reference.