API reference
The backend entrypoint is backend/src/index.ts. Routes are mounted under /api plus an unauthenticated health route at /healthz.
Base URL
Section titled “Base URL”| Environment | URL |
|---|---|
| Development | http://localhost:8787 |
| Production | Your deployed Worker URL |
Authentication
Section titled “Authentication”Protected routes require a valid session. For iOS full-stack flows, requests use:
Authorization: Bearer <session-token>In the current middleware (backend/src/middleware/auth.ts), any request that includes x-test-user-id is treated as authenticated and receives a synthetic session/user payload.
Health check
Section titled “Health check”GET /healthz
Section titled “GET /healthz”Returns server status. No authentication required.
Response:
{ "status": "ok", "environment": "development", "timestamp": "2026-03-01T12:00:00.000Z"}Auth endpoints
Section titled “Auth endpoints”POST /api/auth/sign-in/apple
Section titled “POST /api/auth/sign-in/apple”Custom Apple sign-in endpoint (backend/src/routes/auth.ts).
Request body:
{ "token": "eyJ..."}Also supports optional identityToken, nonce, and appleUserId.
Success response:
{ "user": { "id": "uuid", "email": "user@example.com", "name": "Jane" }, "token": "session-token", "requestId": "cf-ray-id"}GET /api/auth/session
Section titled “GET /api/auth/session”Get the current authenticated session.
Headers: Authorization: Bearer <token>
Response:
{ "user": { "id": "uuid", "email": "...", "name": "..." }, "session": { "id": "uuid", "expiresAt": "..." }, "requestId": "cf-ray-id"}POST /api/auth/sign-out
Section titled “POST /api/auth/sign-out”End the current session.
Headers: Authorization: Bearer <token>
Response:
{ "success": true, "requestId": "cf-ray-id"}ALL /api/auth/*
Section titled “ALL /api/auth/*”Pass-through to better-auth (GET|POST|PUT|PATCH|DELETE|OPTIONS). This covers routes such as email sign-in/sign-up depending on your better-auth configuration.
Profile and settings endpoints
Section titled “Profile and settings endpoints”These routes are mounted via app.route("/api/user", ...) and require auth.
GET /api/user/profile
Section titled “GET /api/user/profile”Returns the current profile:
Response:
{ "data": { "id": "uuid", "email": "user@example.com", "name": "Jane", "image": null }}PUT /api/user/profile
Section titled “PUT /api/user/profile”Updates profile fields:
{ "name": "Jane Doe", "image": "https://example.com/avatar.png"}GET /api/user/settings
Section titled “GET /api/user/settings”Returns current settings:
Response:
{ "data": { "theme": "light", "language": "en", "notifications": true, "analytics": true }}PUT /api/user/settings
Section titled “PUT /api/user/settings”Updates any subset of:
{ "theme": "dark", "language": "en", "notifications": true, "analytics": true}Core API endpoints
Section titled “Core API endpoints”Mounted via app.route("/api", apiRoutes) and auth-protected unless noted.
GET /api/health
Section titled “GET /api/health”Service health + D1 connectivity details.
GET /api/info
Section titled “GET /api/info”Returns service metadata, feature flags, and supported providers.
GET /api/me
Section titled “GET /api/me”Returns current session + user payload from middleware.
GET /api/users
Section titled “GET /api/users”List users (limit, offset query params).
GET /api/users/:id
Section titled “GET /api/users/:id”Get a user by ID.
POST /api/users
Section titled “POST /api/users”Create a user.
Request body:
{ "email": "user@example.com"}PATCH /api/users/:id
Section titled “PATCH /api/users/:id”Update one or more of email, name, image.
DELETE /api/users/:id
Section titled “DELETE /api/users/:id”Delete a user. Returns 204 No Content.
Feedback endpoint
Section titled “Feedback endpoint”POST /api/feedback
Section titled “POST /api/feedback”Requires auth. Persists to the feedback table.
Request body:
{ "category": "bug", "text": "Detailed feedback message", "email": "optional@example.com", "device_info": "optional string or object"}Response (201):
{ "success": true }Chat endpoints
Section titled “Chat endpoints”Mounted via app.route("/api/chat", chatRoutes) and require auth.
POST /api/chat
Section titled “POST /api/chat”Creates a conversation, stores the user message, then streams assistant output.
Request body:
{ "provider": "workers-ai", "model": "@cf/meta/llama-4-scout-17b-16e-instruct", "message": "Explain async/await in Swift", "systemPrompt": null}SSE event types from this route:
event: conversationevent: tokenevent: doneevent: errorGET /api/chat/history
Section titled “GET /api/chat/history”Lists conversation summaries (limit, offset).
GET /api/chat/:id
Section titled “GET /api/chat/:id”Returns one conversation and full message history.
POST /api/chat/:id/message
Section titled “POST /api/chat/:id/message”Appends a message to an existing conversation and streams assistant output.
DELETE /api/chat/:id
Section titled “DELETE /api/chat/:id”Deletes a conversation. Returns 204 No Content.
AI proxy endpoint
Section titled “AI proxy endpoint”POST /api/ai/chat
Section titled “POST /api/ai/chat”Direct provider proxy without conversation persistence.
Request body:
{ "provider": "openai", "model": "gpt-5-mini", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Hello!" } ], "stream": true}Supported providers (from backend/src/services/providers/types.ts):
| Provider | API key env |
|---|---|
openai | OPENAI_API_KEY |
anthropic | ANTHROPIC_API_KEY |
gemini | GEMINI_API_KEY |
workers-ai | Uses Cloudflare AI binding (AI) |
When stream: true, response is streamed (SSE/pass-through).
Rate limiting
Section titled “Rate limiting”Rate limiting middleware is applied to /api/ai/*.
Storage uses RATE_LIMIT_KV when that binding is available, and falls back to an in-memory counter store otherwise.
Default limits:
| Window | Limit | Configurable via |
|---|---|---|
| Per minute | 60 requests | RATE_LIMIT_REQUESTS_PER_MINUTE |
| Per day | 1,000 requests | RATE_LIMIT_REQUESTS_PER_DAY |
Response headers (every request):
X-RateLimit-Limit: 60X-RateLimit-Remaining: 58X-RateLimit-Reset: 1709312400X-RateLimit-Day-Limit: 1000X-RateLimit-Day-Remaining: 997X-RateLimit-Day-Reset: 1709398800When rate-limited (429):
{ "error": "Rate limit exceeded", "code": "RATE_LIMITED", "requestId": "cf-ray-id"}Common headers
Section titled “Common headers”Every response includes:
| Header | Description |
|---|---|
X-Request-Id | Unique request ID (uses cf-ray or UUID) |
Access-Control-Allow-Origin | CORS origin |
Access-Control-Allow-Credentials | true |
Error format
Section titled “Error format”All errors follow a consistent format:
{ "error": "Human-readable error message", "code": "VALIDATION_ERROR", "requestId": "cf-ray-id"}Common status codes:
| Code | Meaning |
|---|---|
400 | Bad request (missing or invalid fields) |
401 | Unauthorized (missing or invalid auth) |
404 | Resource not found |
429 | Rate limit exceeded |
503 | Service unavailable (missing API key config) |