Files
FlockPal/docs/API_REFERENCE.md
T
2026-04-14 22:41:17 -04:00

18 KiB

FlockPal API Reference

This document describes the HTTP API currently implemented in backend/src/app.ts.

Base URLs

  • Development frontend: http://localhost:3000
  • Development API: http://localhost:5000
  • Production API: use your configured BACKEND_URL

Authentication

Most endpoints require a bearer token:

Authorization: Bearer <auth_token>

Tokens are created after either:

  • a successful magic-link sign-in
  • a successful OAuth sign-in with Google, Microsoft, or Apple

The backend redirects the browser back to the frontend with an auth_token query parameter after successful sign-in. Clients should store that token and send it as a bearer token on authenticated requests.

If authentication is missing or invalid, the API returns:

{ "error": "Authentication required." }

FlockPal now supports two bearer token types:

  • browser session tokens returned after magic-link or OAuth sign-in
  • integration tokens created from the Settings UI for automation tools like n8n

Integration tokens are workspace-scoped and support:

  • read_only
  • read_write

How auth_token Is Issued

FlockPal issues the bearer token on the backend after a successful passwordless sign-in. The client does not generate it.

  1. The client calls POST /api/auth/magic-link/request.
  2. The backend creates a short-lived magic-link token and emails it, or returns a preview URL in local development.
  3. The user opens the magic link.
  4. GET /api/auth/magic-link/verify validates the magic-link token, creates an auth session, and generates a new auth_token.
  5. The backend redirects to the frontend with auth_token in the query string.

Example redirect:

http://localhost:3000/?auth_token=YOUR_SESSION_TOKEN

OAuth flow

  1. The client sends the user to GET /api/auth/oauth/{provider}/start.
  2. The provider authenticates the user and redirects back to the backend callback.
  3. The backend callback creates an auth session and generates a new auth_token.
  4. The backend redirects to the frontend with auth_token in the query string.

Example redirect:

http://localhost:3000/?auth_token=YOUR_SESSION_TOKEN

How the token is used

After the frontend receives auth_token, it should store it and send it on authenticated requests:

Authorization: Bearer YOUR_SESSION_TOKEN

Important implementation note

The backend stores only a hash of the session token in the database. The raw token is returned to the client once when the session is created.

Integration tokens follow the same bearer-token header format, but they are created separately and are intended for scripts and automation tools rather than browser login.

Roles

Workspace roles used by protected endpoints:

  • owner
  • manager
  • staff
  • viewer

Role requirements are called out per endpoint below. If the signed-in member lacks permission, the API returns:

{ "error": "You do not have permission for that action." }

Data Shapes

User

{
  "id": "uuid",
  "email": "person@example.com",
  "name": "Taylor",
  "createdAt": "2026-04-14T12:34:56.000Z"
}

Workspace

{
  "id": 1001,
  "name": "Home Flock",
  "workspaceType": "standard",
  "billingEmail": "billing@example.com",
  "billingPlan": "household_basic",
  "createdAt": "2026-04-14T12:34:56.000Z",
  "updatedAt": "2026-04-14T12:34:56.000Z"
}

Workspace Member

{
  "id": "uuid",
  "workspaceId": 1001,
  "userId": "uuid",
  "inviteEmail": "member@example.com",
  "name": "Alex",
  "role": "viewer",
  "acceptedAt": "2026-04-14T12:34:56.000Z",
  "createdAt": "2026-04-14T12:34:56.000Z"
}

Bird

{
  "id": "uuid",
  "workspaceId": 1001,
  "name": "Kiwi",
  "tagId": "FP-001",
  "species": "Cockatiel",
  "dateOfBirth": "2023-05-10",
  "gotchaDay": "2023-08-21",
  "chartColor": "#cb3a35",
  "photoDataUrl": null,
  "notifyOnDob": false,
  "notifyOnGotchaDay": true,
  "createdAt": "2026-04-14T12:34:56.000Z",
  "latestWeightGrams": 92,
  "latestRecordedOn": "2026-04-14"
}

Weight

{
  "id": "uuid",
  "birdId": "uuid",
  "weightGrams": 92,
  "recordedOn": "2026-04-14",
  "notes": "Morning check"
}

Vet Visit

{
  "id": "uuid",
  "birdId": "uuid",
  "visitedOn": "2026-04-14",
  "clinicName": "Avian Care Center",
  "reason": "Wellness exam",
  "notes": "Healthy"
}

Common Validation Rules

  • Dates use YYYY-MM-DD
  • workspaceType is standard or rescue
  • member role is owner, manager, staff, or viewer
  • bird chartColor must be a #RRGGBB hex color
  • photoDataUrl must be a base64 data:image/... URL
  • weightGrams must be a positive number up to 10000

Validation failures return 400 with this shape:

{
  "error": "Invalid ... payload",
  "details": {}
}

Endpoints

Health

GET /api/health

Public health check.

Response 200:

{ "ok": true }

Authentication

GET /api/auth/providers

Public list of configured OAuth providers.

Response 200:

{
  "providers": [
    {
      "providerKey": "google",
      "displayName": "Google",
      "enabled": true
    }
  ]
}

POST /api/auth/register

Password registration is disabled.

Response 410:

{
  "error": "Password-based registration is disabled. Use a magic link or an identity provider."
}

POST /api/auth/login

Password sign-in is disabled.

Response 410:

{
  "error": "Password-based sign-in is disabled. Use a magic link or an identity provider."
}

POST /api/auth/magic-link/request

Starts a passwordless sign-in flow.

Request body:

{
  "email": "person@example.com",
  "name": "Taylor",
  "redirectTo": "http://localhost:3000"
}

Notes:

  • name is optional
  • redirectTo is optional and defaults to the frontend base URL
  • if email delivery is not configured, the API returns a preview URL instead

Response 202:

{
  "ok": true,
  "message": "If that address can sign in, a magic link is on the way.",
  "previewUrl": "http://localhost:5000/api/auth/magic-link/verify?token=...",
  "delivery": "preview"
}

curl example:

curl -X POST http://localhost:5000/api/auth/magic-link/request \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "person@example.com",
    "name": "Taylor",
    "redirectTo": "http://localhost:3000"
  }'

Local-development example response when SMTP is not configured:

{
  "ok": true,
  "message": "If that address can sign in, a magic link is on the way.",
  "previewUrl": "http://localhost:5000/api/auth/magic-link/verify?token=...",
  "delivery": "preview"
}

GET /api/auth/magic-link/verify?token=...

Consumes a single-use magic-link token, creates or loads the user, creates a session, and redirects to the frontend with auth_token in the query string.

Responses:

  • 302 redirect on success
  • 400 if the token is missing, invalid, or expired

If you are testing locally and received a previewUrl, open that URL in a browser or inspect the redirect target to capture the auth_token.

POST /api/auth/logout

Requires auth. Invalidates the current session.

Response 204 with no body.

GET /api/auth/session

Requires auth. Returns the current session context.

Response 200:

{
  "token": "raw-session-token",
  "session": {
    "user": {
      "id": "uuid",
      "email": "person@example.com",
      "name": "Taylor",
      "createdAt": "2026-04-14T12:34:56.000Z"
    },
    "activeWorkspace": {
      "id": 1001,
      "name": "Home Flock",
      "workspaceType": "standard",
      "billingEmail": null,
      "billingPlan": "household_basic",
      "createdAt": "2026-04-14T12:34:56.000Z",
      "updatedAt": "2026-04-14T12:34:56.000Z"
    },
    "activeMembership": {
      "id": "uuid",
      "workspaceId": 1001,
      "userId": "uuid",
      "inviteEmail": "person@example.com",
      "name": "Taylor",
      "role": "owner",
      "acceptedAt": "2026-04-14T12:34:56.000Z",
      "createdAt": "2026-04-14T12:34:56.000Z"
    },
    "workspaces": [],
    "providers": []
  }
}

curl example:

curl http://localhost:5000/api/auth/session \
  -H 'Authorization: Bearer YOUR_SESSION_TOKEN'

POST /api/auth/switch-workspace

Requires auth. Switches the session's active workspace.

Request body:

{
  "workspaceId": 1002
}

Response 200 returns the same shape as GET /api/auth/session.

curl example:

curl -X POST http://localhost:5000/api/auth/switch-workspace \
  -H 'Authorization: Bearer YOUR_SESSION_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "workspaceId": 1002
  }'

Possible errors:

  • 403 if the user is not a member of the requested workspace

GET /api/auth/oauth/{provider}/start

Starts an OAuth login flow and redirects to the external identity provider.

Path params:

  • provider: google, microsoft, or apple

Concrete examples:

  • /api/auth/oauth/google/start
  • /api/auth/oauth/microsoft/start
  • /api/auth/oauth/apple/start

Query params:

  • redirectTo optional frontend redirect target after successful login

Responses:

  • 302 redirect to provider on success
  • 404 for an unknown provider
  • 400 if the provider is not configured

Browser-oriented example:

http://localhost:5000/api/auth/oauth/google/start?redirectTo=http://localhost:3000

curl can show the initial redirect, but this flow is meant to complete in a browser:

curl -i "http://localhost:5000/api/auth/oauth/google/start?redirectTo=http://localhost:3000"

GET /api/auth/oauth/{provider}/callback

POST /api/auth/oauth/{provider}/callback

OAuth callback used by providers. On success, the backend redirects to the frontend with auth_token in the query string.

Path params:

  • provider: google, microsoft, or apple

Responses:

  • 302 redirect on success
  • 400 for missing or expired OAuth state
  • 404 for unknown provider

Transfers

POST /api/transfers/draft

Requires auth. Prepares a bird transfer to another owner email and optionally triggers a magic-link invite for that email when no user exists yet.

Request body:

{
  "birdId": "uuid",
  "destinationOwnerEmail": "new-owner@example.com",
  "notes": "Optional draft note"
}

Response 201:

{
  "ok": true,
  "bird": {},
  "destinationOwnerEmail": "new-owner@example.com",
  "destinationOwnerExists": false,
  "inviteSent": true,
  "invitePreviewUrl": "http://localhost:5000/api/auth/magic-link/verify?token=...",
  "inviteDelivery": "preview"
}

Possible errors:

  • 404 if the bird is not in the active workspace

Workspaces

GET /api/workspaces

Requires auth. Lists the signed-in user's workspace memberships.

Response 200:

{
  "workspaces": []
}

POST /api/workspaces

Requires auth. Creates a new workspace and makes the current user its owner.

Request body:

{
  "name": "Home Flock",
  "workspaceType": "standard",
  "billingEmail": "billing@example.com",
  "billingPlan": "household_plus"
}

Notes:

  • workspaceType must be standard or rescue
  • billingPlan may be household_basic, household_plus, or household_macaw
  • rescue workspaces are forced to rescue_free

Response 201:

{
  "workspace": {}
}

GET /api/workspace

Requires auth. Returns the active workspace. Browser sessions and integration tokens can both use this endpoint.

Response 200:

{
  "workspace": {}
}

PUT /api/workspace

Requires auth and role owner or manager. Updates the active workspace.

Request body:

{
  "name": "Updated Flock",
  "workspaceType": "standard",
  "billingEmail": "billing@example.com",
  "billingPlan": "household_basic"
}

Response 200:

{
  "workspace": {}
}

GET /api/workspace/members

Requires auth. Lists members for the active workspace. Browser sessions and integration tokens can both use this endpoint.

Response 200:

{
  "members": []
}

POST /api/workspace/members

Requires auth and role owner or manager. Invites or upserts a workspace member.

Request body:

{
  "name": "Alex",
  "email": "alex@example.com",
  "role": "viewer"
}

Response 201:

{
  "member": {}
}

DELETE /api/workspace/members/:memberId

Requires auth and role owner or manager. Removes a non-owner member.

Response 204 with no body.

Possible errors:

  • 404 if the member was not found or is an owner

Birds

GET /api/birds

Requires auth. Lists birds in the active workspace. Browser sessions and integration tokens can both use this endpoint.

Response 200:

{
  "birds": []
}

POST /api/birds

Requires auth and role owner, manager, or staff. Creates a bird.

Request body:

{
  "name": "Kiwi",
  "tagId": "FP-001",
  "species": "Cockatiel",
  "dateOfBirth": "2023-05-10",
  "gotchaDay": "2023-08-21",
  "chartColor": "#cb3a35",
  "photoDataUrl": "",
  "notifyOnDob": false,
  "notifyOnGotchaDay": true
}

Notes:

  • dateOfBirth, gotchaDay, and photoDataUrl may be omitted or sent as empty strings
  • chartColor defaults to #cb3a35

Response 201:

{
  "bird": {}
}

Possible errors:

  • 409 if the workspace already uses that tagId

PUT /api/birds/:birdId

Requires auth and role owner, manager, or staff. Updates a bird.

Request body matches POST /api/birds.

Response 200:

{
  "bird": {}
}

Possible errors:

  • 404 if the bird does not exist in the active workspace
  • 409 if the workspace already uses that tagId

DELETE /api/birds/:birdId

Requires auth and role owner, manager, or staff. Deletes a bird.

Response 204 with no body.

Possible errors:

  • 404 if the bird does not exist in the active workspace

Weights

GET /api/birds/:birdId/weights

Requires auth. Lists weight entries for a bird in the active workspace.

Query params:

  • days optional, clamped to 1 through 365, default 30

Response 200:

{
  "weights": []
}

POST /api/birds/:birdId/weights

Requires auth and role owner, manager, or staff. Creates a weight entry.

Request body:

{
  "weightGrams": 92,
  "recordedOn": "2026-04-14",
  "notes": "Morning check"
}

Response 201:

{
  "weight": {}
}

Possible errors:

  • 404 if the bird does not exist in the active workspace
  • 409 if a weight already exists for that bird on that date

Vet Visits

GET /api/birds/:birdId/vet-visits

Requires auth. Lists vet visits for a bird in the active workspace.

Response 200:

{
  "vetVisits": []
}

POST /api/birds/:birdId/vet-visits

Requires auth and role owner, manager, or staff. Creates a vet visit.

Request body:

{
  "visitedOn": "2026-04-14",
  "clinicName": "Avian Care Center",
  "reason": "Wellness exam",
  "notes": "Healthy"
}

Response 201:

{
  "vetVisit": {}
}

Possible errors:

  • 404 if the bird does not exist in the active workspace

Integration Tokens

These endpoints are for browser-session users managing their own automation tokens. They are not accessible with an integration token itself.

GET /api/integration-tokens

Requires a browser session. Lists the current user's active integration tokens for the active workspace.

Response 200:

{
  "integrationTokens": [
    {
      "id": "uuid",
      "userId": "uuid",
      "workspaceId": 1001,
      "name": "n8n household sync",
      "tokenPrefix": "flpt_1234abcd56",
      "scope": "read_write",
      "lastUsedAt": "2026-04-14T12:34:56.000Z",
      "expiresAt": null,
      "revokedAt": null,
      "createdAt": "2026-04-14T12:00:00.000Z"
    }
  ]
}

POST /api/integration-tokens

Requires a browser session. Creates a new integration token for the active workspace and returns the raw token once.

Request body:

{
  "name": "n8n household sync",
  "scope": "read_write",
  "expiresInDays": 90
}

Notes:

  • scope may be read_only or read_write
  • expiresInDays is optional
  • the raw token is only returned at creation time

Response 201:

{
  "integrationToken": {
    "id": "uuid",
    "userId": "uuid",
    "workspaceId": 1001,
    "name": "n8n household sync",
    "tokenPrefix": "flpt_1234abcd56",
    "scope": "read_write",
    "lastUsedAt": null,
    "expiresAt": "2026-07-13T12:00:00.000Z",
    "revokedAt": null,
    "createdAt": "2026-04-14T12:00:00.000Z"
  },
  "token": "flpt_..."
}

curl example:

curl -X POST http://localhost:5000/api/integration-tokens \
  -H 'Authorization: Bearer YOUR_SESSION_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "n8n household sync",
    "scope": "read_write",
    "expiresInDays": 90
  }'

Use the returned token in your automation tool:

Authorization: Bearer flpt_...

DELETE /api/integration-tokens/{tokenId}

Requires a browser session. Revokes an integration token owned by the current user in the active workspace.

Response 204 with no body.

Error Summary

Common status codes used by the API:

  • 200 successful read or update
  • 201 resource created
  • 202 async or queued success for magic-link requests
  • 204 successful delete or logout with no response body
  • 400 invalid request payload or expired callback state
  • 401 authentication required
  • 403 authenticated but not authorized for the action
  • 404 resource or provider not found
  • 409 uniqueness conflict
  • 410 password-based auth endpoints intentionally disabled

Source of Truth

This document reflects the routes currently implemented in:

  • backend/src/app.ts

If the docs and code ever disagree, treat the code as the source of truth.

Quick curl Workflow

Basic local-development auth check:

  1. Request a magic link:
curl -X POST http://localhost:5000/api/auth/magic-link/request \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "person@example.com",
    "name": "Taylor",
    "redirectTo": "http://localhost:3000"
  }'
  1. Copy the previewUrl from the response if local email is not configured.

  2. Open that URL in a browser and capture auth_token from the frontend redirect URL.

  3. Use the token:

curl http://localhost:5000/api/auth/session \
  -H 'Authorization: Bearer YOUR_SESSION_TOKEN'