Skip to content

REST API

The Flowstate REST API provides HTTP endpoints for the web application. All requests are routed through the Astro server-side proxy, which forwards them to the backend service.

All API endpoints are accessed through the Astro server proxy:

https://app.goflowstate.com/api/*

The web application uses relative URLs (/api/*) which are automatically proxied to the backend service via the SERVER_API_URL environment variable.

The REST API uses cookie-based JWT authentication. After successful login, the server sets a flowstate_session cookie containing a signed JWT token.

Authentication Flow:

  1. User requests magic link via /api/auth/request-login
  2. User clicks link in email, which redirects to /api/auth/authorize
  3. Server validates token and sets flowstate_session cookie
  4. Subsequent requests include cookie automatically
  5. Server validates JWT on protected routes

Cookie Details:

  • Name: flowstate_session
  • HttpOnly: true
  • Secure: true (production only)
  • SameSite: Lax
  • Max-Age: 30 days

All API responses follow a consistent structure.

{
"data": {
"id": "spark_abc123",
"title": "My Spark",
"createdAt": "2026-02-28T10:30:00.000Z"
}
}
{
"data": [
{ "id": "room_001", "name": "Design Team" },
{ "id": "room_002", "name": "Engineering" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 42,
"totalPages": 3
}
}
{
"error": "ValidationError",
"message": "Invalid room name: must be between 1 and 100 characters",
"details": [
{
"field": "name",
"issue": "String must contain at least 1 character(s)"
}
]
}

List endpoints support pagination via query parameters:

  • page (default: 1) - Page number (1-indexed)
  • limit (default: 20, max: 100) - Items per page

Example Request:

GET /api/rooms?page=2&limit=50

Response:

{
"data": [...],
"pagination": {
"page": 2,
"limit": 50,
"total": 127,
"totalPages": 3
}
}

The API is organized into logical route groups:

GroupPathDescription
Auth/api/authMagic link authentication, login, logout, session refresh, account deletion
Rooms/api/roomsRoom creation, listing, updates, deletion
Sparks/api/sparksSpark creation, editing, positioning, deletion
Offices/api/officesOffice management, invites, membership, settings
Passport/api/passportUser profile management, CV import, skills
Shelves/api/shelvesShelf and link management, ordering
Canvas/api/canvasTile position updates, layout computation, collision detection
Sync/api/syncServer-Sent Events subscription for real-time updates
Uploads/api/uploadsFile upload and serving (avatars, attachments)
Agora/api/agoraVideo/voice chat token generation, configuration
Users/api/usersUser profile and display information
Reports/api/reportsContent reporting (spam, abuse, etc.)
Appeals/api/appealsContent appeal submission and tracking
Widgets/api/widgetsWidget management, installation, settings
MOTD/api/motdMessage of the day management
Health/healthSystem health check and service status
POST /api/auth/request-login
Content-Type: application/json
{
"email": "[email protected]"
}

Sends a magic link to the provided email address. Returns success even if email doesn’t exist (prevents email enumeration).

GET /api/auth/authorize?token=<magic_link_token>

Validates the magic link token and sets the session cookie. Redirects to the application on success.

GET /api/auth/me

Returns the currently authenticated user’s profile.

Response:

{
"data": {
"id": "user_abc123",
"email": "[email protected]",
"displayName": "Jane Doe",
"avatarUrl": "https://storage.goflowstate.com/avatars/abc123.jpg",
"createdAt": "2026-01-15T08:00:00.000Z"
}
}
POST /api/auth/logout

Clears the session cookie and invalidates the JWT token.

POST /api/auth/request-deletion

Initiates account deletion flow. Sends confirmation email with deletion token.

POST /api/auth/confirm-deletion
Content-Type: application/json
{
"token": "<deletion_token>"
}

Permanently deletes the user account and all associated data.

GET /api/rooms?page=1&limit=20

Returns all rooms the authenticated user has access to.

POST /api/rooms
Content-Type: application/json
{
"name": "Design Team",
"description": "Collaborative design workspace",
"isPublic": false
}
GET /api/rooms/:roomId
PATCH /api/rooms/:roomId
Content-Type: application/json
{
"name": "Updated Room Name",
"description": "New description"
}
DELETE /api/rooms/:roomId

Only the room creator can delete a room.

POST /api/sparks
Content-Type: application/json
{
"title": "Project Kickoff",
"content": "Let's discuss the roadmap for Q2",
"roomId": "room_abc123",
"x": 100,
"y": 200
}
PATCH /api/sparks/:sparkId
Content-Type: application/json
{
"title": "Updated Title",
"content": "Updated content"
}
PATCH /api/sparks/:sparkId/position
Content-Type: application/json
{
"x": 300,
"y": 400
}
DELETE /api/sparks/:sparkId
GET /api/offices?page=1&limit=20
POST /api/offices
Content-Type: application/json
{
"name": "Engineering Office",
"slug": "engineering",
"description": "Daily standup and collaboration space"
}
GET /api/offices/:officeId
POST /api/offices/:officeId/invites
Content-Type: application/json
{
"email": "[email protected]",
"role": "member"
}

Roles: owner, admin, member

GET /api/offices/:officeId/members
DELETE /api/offices/:officeId/members/:userId
PATCH /api/canvas/position
Content-Type: application/json
{
"tileId": "spark_abc123",
"x": 500,
"y": 600
}

Applies snap-to-grid and collision detection automatically.

PATCH /api/canvas/positions/batch
Content-Type: application/json
{
"updates": [
{ "tileId": "spark_001", "x": 100, "y": 100 },
{ "tileId": "spark_002", "x": 200, "y": 200 }
]
}
POST /api/canvas/layout/compute
Content-Type: application/json
{
"roomId": "room_abc123",
"algorithm": "force-directed"
}

Automatically arranges tiles using the specified layout algorithm.

POST /api/uploads
Content-Type: multipart/form-data
file: <binary data>
type: "avatar" | "attachment"

Returns the uploaded file’s URL.

Response:

{
"data": {
"url": "https://storage.goflowstate.com/uploads/abc123.jpg",
"filename": "profile.jpg",
"size": 245678,
"mimeType": "image/jpeg"
}
}
GET /api/uploads/:fileId

Serves the uploaded file with appropriate Content-Type headers.

GET /api/sync/subscribe
Accept: text/event-stream

Establishes a Server-Sent Events connection for real-time updates. See Real-Time Sync for details.

GET /health

Returns the health status of all services.

Response:

{
"status": "healthy",
"timestamp": "2026-02-28T10:30:00.000Z",
"services": {
"database": {
"status": "healthy",
"latency": 12
},
"redis": {
"status": "healthy",
"latency": 3
},
"storage": {
"status": "healthy",
"latency": 45
}
},
"uptime": 86400
}

The API uses standard HTTP status codes:

CodeMeaningDescription
200OKRequest succeeded
201CreatedResource created successfully
204No ContentRequest succeeded with no response body
400Bad RequestInvalid request parameters or validation failure
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but lacking permissions
404Not FoundResource doesn’t exist
409ConflictResource conflict (duplicate, constraint violation)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableService temporarily unavailable

All endpoints are rate-limited to prevent abuse:

  • Default limit: 100 requests per minute per IP
  • Auth endpoints: 10 requests per minute per IP
  • Upload endpoints: 20 requests per minute per user

When rate limited, the API returns a 429 status with a Retry-After header indicating when to retry.

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1709118600

The backend enforces CORS based on the CORS_ORIGIN environment variable:

  • Production: https://app.goflowstate.com

Credentials (cookies) are allowed for same-origin requests.

All request bodies are validated using Zod schemas. Validation errors return a 400 status with detailed error information:

{
"error": "ValidationError",
"message": "Invalid request body",
"details": [
{
"field": "email",
"issue": "Invalid email address"
},
{
"field": "name",
"issue": "String must contain at least 1 character(s)"
}
]
}
  1. Always check response status before parsing the body
  2. Handle rate limits gracefully by respecting Retry-After headers
  3. Use pagination for list endpoints to avoid large payloads
  4. Include error handling for all API calls
  5. Validate input client-side before sending requests (reduces round trips)
  6. Use relative URLs (/api/*) in client code (works with the proxy)
  7. Never expose SERVER_API_URL to the browser (server-side only)