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_onlyread_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.
Magic-link flow
- The client calls
POST /api/auth/magic-link/request. - The backend creates a short-lived magic-link token and emails it, or returns a preview URL in local development.
- The user opens the magic link.
GET /api/auth/magic-link/verifyvalidates the magic-link token, creates an auth session, and generates a newauth_token.- The backend redirects to the frontend with
auth_tokenin the query string.
Example redirect:
http://localhost:3000/?auth_token=YOUR_SESSION_TOKEN
OAuth flow
- The client sends the user to
GET /api/auth/oauth/{provider}/start. - The provider authenticates the user and redirects back to the backend callback.
- The backend callback creates an auth session and generates a new
auth_token. - The backend redirects to the frontend with
auth_tokenin 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:
ownermanagerstaffviewer
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 workspaceTypeisstandardorrescue- member
roleisowner,manager,staff, orviewer - bird
chartColormust be a#RRGGBBhex color photoDataUrlmust be a base64data:image/...URLweightGramsmust be a positive number up to10000
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:
nameis optionalredirectTois 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:
302redirect on success400if 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:
403if 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, orapple
Concrete examples:
/api/auth/oauth/google/start/api/auth/oauth/microsoft/start/api/auth/oauth/apple/start
Query params:
redirectTooptional frontend redirect target after successful login
Responses:
302redirect to provider on success404for an unknown provider400if 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, orapple
Responses:
302redirect on success400for missing or expired OAuth state404for 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:
404if 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:
workspaceTypemust bestandardorrescuebillingPlanmay behousehold_basic,household_plus, orhousehold_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:
404if 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, andphotoDataUrlmay be omitted or sent as empty stringschartColordefaults to#cb3a35
Response 201:
{
"bird": {}
}
Possible errors:
409if the workspace already uses thattagId
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:
404if the bird does not exist in the active workspace409if the workspace already uses thattagId
DELETE /api/birds/:birdId
Requires auth and role owner, manager, or staff. Deletes a bird.
Response 204 with no body.
Possible errors:
404if 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:
daysoptional, clamped to1through365, default30
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:
404if the bird does not exist in the active workspace409if 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:
404if 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:
scopemay beread_onlyorread_writeexpiresInDaysis 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:
200successful read or update201resource created202async or queued success for magic-link requests204successful delete or logout with no response body400invalid request payload or expired callback state401authentication required403authenticated but not authorized for the action404resource or provider not found409uniqueness conflict410password-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:
- 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"
}'
-
Copy the
previewUrlfrom the response if local email is not configured. -
Open that URL in a browser and capture
auth_tokenfrom the frontend redirect URL. -
Use the token:
curl http://localhost:5000/api/auth/session \
-H 'Authorization: Bearer YOUR_SESSION_TOKEN'