Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt

Use this file to discover all available pages before exploring further.

Caret delegates identity and token issuance entirely to Supabase Auth. When a user signs in through the Caret frontend, Supabase issues a JSON Web Token (JWT) that encodes the user’s identity, expiry time, and role claims. Every Caret backend service validates that token server-side before processing a request — no session cookies, no API keys for user-facing calls. Pass the JWT as a Bearer token in the Authorization header on every request to the API Gateway.

How Authentication Works

1

User signs in via Supabase Auth

The Caret frontend uses the Supabase JS client to authenticate the user with email/password (or an OAuth provider). Supabase Auth issues a short-lived access token and a longer-lived refresh token, both stored in the browser session managed by the Supabase client.
2

Frontend attaches the JWT to API requests

Before making any REST call to the Caret API, the frontend reads session.access_token from the Supabase client and attaches it to the Authorization header as a Bearer token.
3

API Gateway validates the token

The API Gateway’s auth middleware verifies the JWT signature using SUPABASE_JWT_SECRET. If the token is missing, malformed, or expired, the gateway returns 401 Unauthorized before the request reaches any downstream service.
4

Downstream services enforce permissions

Services such as document-service and ai-service extract the user ID from the validated JWT and apply row-level security (RLS) policies in the database to ensure users can only access resources they own or have been invited to.

Obtaining a Token

Use the Supabase JS client in your frontend application to sign in and retrieve the access token. Caret uses Google OAuth as its sign-in provider:
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// Sign in with Google OAuth — redirects to Google and back to your app
const { error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://app.caret.page/auth/callback',
  },
})

if (error) {
  console.error('Sign-in failed:', error.message)
}

// After the OAuth redirect, retrieve the active session
const { data: { session } } = await supabase.auth.getSession()

// The access token to attach to API requests
const token = session?.access_token
Backend services validate JWTs server-side using SUPABASE_JWT_SECRET. The SUPABASE_ANON_KEY is for client-side Supabase calls only — it authorises the frontend to interact with Supabase directly (for example reading user_profiles) but is not the same as the JWT that you send to the Caret API Gateway.

Making Authenticated Requests

Once you have the access token, include it in the Authorization header on every call to the Caret API:
curl -X GET https://api.caret.page/api/v1/documents \
  -H "Authorization: Bearer eyJ..."
A complete example — create a new document in an existing workspace:
curl -X POST https://api.caret.page/api/v1/documents \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q3 Planning Notes",
    "workspace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }'
A successful response returns 201 Created with the new document:
{
  "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
  "workspace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "folder_id": null,
  "title": "Q3 Planning Notes",
  "status": "active",
  "visibility": "private",
  "owner_user_id": "user-uuid-here",
  "content_json": null,
  "content_text": null,
  "created_at": "2024-01-15T10:30:00.000Z",
  "updated_at": "2024-01-15T10:30:00.000Z"
}
If the token is missing or invalid, the API returns:
{
  "error": "Unauthorized"
}

Token Expiry and Refresh

Supabase JWTs are short-lived. The Supabase JS client handles refresh automatically using the onAuthStateChange listener. Wire this up once in your application bootstrap to ensure tokens are always fresh before being attached to requests:
supabase.auth.onAuthStateChange((_event, session) => {
  // session.access_token is the latest valid token after any auth state change
  const token = session?.access_token ?? null

  if (token) {
    // Update your API client or request interceptor with the fresh token
    console.log('Token updated:', token)
  } else {
    // User signed out — clear stored credentials
    console.log('User signed out')
  }
})
If you are building a server-side integration (for example a Next.js server component or a backend script), use the Supabase Admin client with SUPABASE_SERVICE_ROLE_KEY for privileged operations, or implement the OAuth Authorization Code Flow to obtain user-scoped tokens without a browser session. Never hard-code access tokens in scripts or repositories.

Authenticating the Collaboration WebSocket

The collaboration service runs separately from the API Gateway and does not use HTTP headers for auth. Instead, pass the JWT as a token query parameter when opening the WebSocket connection:
wss://ws.caret.page/document/{doc_id}?token={jwt}
Local development equivalent:
ws://localhost:3003/document/{doc_id}?token={jwt}
The collab service validates the Supabase JWT during the WebSocket handshake. If the token is absent or invalid, the connection is rejected immediately. The JWT must belong to a user with access to the specified document.
The collaboration WebSocket connects directly to ws.caret.page (or localhost:3003 locally) — it does not route through the API Gateway at api.caret.page. Ensure your firewall and CORS configuration allow the frontend origin to reach both hosts.

Environment Variables

The table below lists every environment variable involved in Caret’s authentication flow, who owns it, and what it is used for.
VariableScopePurpose
SUPABASE_URLFrontend + BackendBase URL of your Supabase project (e.g. https://xyz.supabase.co)
SUPABASE_ANON_KEYFrontend onlyPublic anon key for client-side Supabase JS calls
SUPABASE_JWT_SECRETBackend servicesSecret used to verify JWT signatures on incoming requests
SUPABASE_SERVICE_ROLE_KEYBackend services onlyPrivileged service-role key for server-side admin operations
Never expose SUPABASE_SERVICE_ROLE_KEY to the frontend or include it in any client-side bundle. This key bypasses all Row Level Security policies and grants unrestricted database access. Store it exclusively in server-side environment variables and secrets managers, and rotate it immediately if it is ever leaked.

Summary

REST API

Pass the Supabase JWT as Authorization: Bearer <token> on every call to https://api.caret.page/api/v1/.... Tokens are validated server-side by the API Gateway.

Collaboration WebSocket

Pass the JWT as a query parameter: wss://ws.caret.page/document/{doc_id}?token={jwt}. Validated by the collab service during the WebSocket handshake.

Build docs developers (and LLMs) love