API Overview
Conventions
- All request and response bodies use JSON (unless streaming).
- Board-scoped endpoints include
boardIdin the path:/api/boards/{boardId}/.... - AI endpoints that produce streamed content use Server-Sent Events via the Vercel AI SDK
toUIMessageStreamResponse()or plain text streams. - All timestamps are ISO 8601 strings.
Authentication
Most endpoints require a valid session cookie (session HttpOnly cookie set after OTP login).
Exceptions (no auth required):
| Endpoint | Reason |
|---|---|
POST /api/auth/send-otp | Initiates the login flow |
POST /api/auth/verify-otp | Completes login |
POST /api/billing/webhook | Stripe uses its own signature verification |
Board access
Board-scoped endpoints validate both the session and the user's board access level using a centralised boardGuard() middleware. A valid session is not sufficient — the user must also be the owner or an invited collaborator.
Error codes
| Code | Meaning | Common cause |
|---|---|---|
400 | Bad request | Invalid or missing payload field; Zod validation failure |
401 | Unauthorised | Missing or expired session cookie |
403 | Forbidden | Valid session but no board access |
429 | Too many requests | AI rate limit or monthly quota exceeded |
500 | Server error | Unexpected runtime error |
503 | Service unavailable | Cloudflare context unavailable (production config issue) |
Request patterns
Authenticated board request
GET /api/boards/{boardId}/sync?since=42
Cookie: session=<token>AI streaming request
POST /api/canvas/chat
Cookie: session=<token>
Content-Type: application/json
{
"boardId": "abc123",
"messages": [...],
"boardState": { ... }
}Response: SSE stream (text/event-stream).
Quota-limited AI request
When a quota is exceeded, the response is:
HTTP 429
{ "error": "AI quota exceeded", "used": 20, "limit": 20, "plan": "free" }See also
- Auth API — Login endpoints
- Sync API — Push/pull payload details
- Authentication and Access — Session model