21 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 protected endpoints use a bearer token:
Authorization: Bearer <token>
The current backend supports two token systems.
1. Browser session tokens
Browser session tokens are created after:
- a successful magic-link sign-in
- a successful OAuth sign-in with Google, Microsoft, or Apple
After sign-in, the backend redirects the browser back to the frontend with an auth_token query parameter. That auth_token is the browser session token.
Example redirect:
http://localhost:3000/?auth_token=YOUR_SESSION_TOKEN
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.
2. Integration tokens
Integration tokens are created by an authenticated browser-session user through:
GET /api/integration-tokensPOST /api/integration-tokensDELETE /api/integration-tokens/:tokenId
These tokens are intended for automation tools such as n8n, scripts, and server-to-server use.
Integration tokens are:
- workspace-scoped
- tied to the creating user
- returned in raw form only once at creation time
- either
read_onlyorread_write
Use them exactly like session tokens:
Authorization: Bearer flpt_...
Token behavior by endpoint type
Browser-session-only endpoints:
/api/auth/logout/api/auth/session/api/auth/switch-workspace/api/workspaces/api/integration-tokens
Endpoints that accept either browser session tokens or integration tokens:
/api/workspace/api/workspace/members/api/birds/api/birds/:birdId/weights/api/birds/:birdId/vet-visits/api/transfers/draft
Read-only integration tokens can call read endpoints, but cannot call write endpoints.
If authentication is missing or invalid, the API returns:
{ "error": "Authentication required." }
Browser-session-only endpoints reject integration tokens with:
{
"error": "This endpoint requires a browser session instead of an integration token."
}
Write endpoints reject read-only integration tokens with:
{
"error": "That integration token is read-only."
}
How Browser auth_token Is Issued
FlockPal issues the browser 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 browser token is used
After the frontend receives auth_token, it should store it and send it on authenticated requests:
Authorization: Bearer YOUR_SESSION_TOKEN
Integration tokens use the same bearer-token header format, but they are created separately and are not used for browser login.
Roles
Workspace roles used by protected endpoints:
ownerassistantcaregiverviewer
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",
"gender": "female",
"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,assistant,caregiver, orviewer - bird
genderisunknown,male, orfemale - 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 a browser session. Invalidates the current session.
Response 204 with no body.
GET /api/auth/session
Requires a browser session. 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 a browser session. 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 with write access. 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 a browser session. Lists the signed-in user's workspace memberships.
Response 200:
{
"workspaces": []
}
POST /api/workspaces
Requires a browser session. 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 with write access and role owner or assistant. Updates the active workspace.
Request body:
{
"name": "Updated Flock",
"workspaceType": "standard",
"billingEmail": "billing@example.com",
"billingPlan": "household_basic"
}
Response 200:
{
"workspace": {}
}
DELETE /api/workspace
Requires a browser session and role owner. Deletes the active flock only when it has no birds.
Behavior:
- if the flock still has birds, deletion is blocked
- collaborators, sessions, and integration tokens tied to the flock are removed with it
- the backend switches the user to another existing flock, or creates a new personal flock automatically if needed
Response 200:
{
"deletedWorkspaceId": 1001,
"token": "raw-session-token",
"session": {}
}
Possible errors:
409if birds are still assigned to the flock404if the flock no longer exists
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 with write access and role owner or assistant. 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 with write access and role owner or assistant. 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 with write access and role owner, assistant, or caregiver. Creates a bird.
Request body:
{
"name": "Kiwi",
"tagId": "FP-001",
"species": "Cockatiel",
"gender": "female",
"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 with write access and role owner, assistant, or caregiver. 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
POST /api/birds/:birdId/transfer
Requires a browser session, write access, and role owner or assistant. Moves a bird from the active flock to another flock the same user can access.
Request body:
{
"targetWorkspaceId": 1002
}
Notes:
- the destination flock must be different from the current flock
- the signed-in user must already be a member of the destination flock
- the bird keeps its existing weight and vet history because the record itself is reassigned
Response 200:
{
"bird": {}
}
Possible errors:
400if the destination flock is the current flock or the payload is invalid403if the user does not have access to the destination flock404if the bird is not in the active flock409if the destination flock already has a bird using the sametagId
DELETE /api/birds/:birdId
Requires auth with write access and role owner, assistant, or caregiver. 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 with write access and role owner, assistant, or caregiver. 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 with write access and role owner, assistant, or caregiver. 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'