26 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/birds/:birdId/medications
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",
"vetClinicName": "Avian Care Center",
"vetClinicAddress": "123 Feather Lane, Raleigh, NC",
"vetAccountNumber": "FP-1001",
"vetDoctorName": "Dr. Rivera",
"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"
}
Medication
{
"id": "uuid",
"birdId": "uuid",
"name": "Meloxicam",
"dosage": "0.05 mL",
"frequency": "twice_daily",
"doseSchedule": [
{
"key": "dose-1",
"label": "Morning",
"time": "08:00"
},
{
"key": "dose-2",
"label": "Evening",
"time": "20:00"
}
],
"route": "Oral",
"startDate": "2026-04-14",
"endDate": null,
"notes": "Give with food"
}
Medication Administration
{
"id": "uuid",
"medicationId": "uuid",
"birdId": "uuid",
"administeredOn": "2026-04-14",
"administrationSlot": "dose-1",
"status": "administered",
"notes": null,
"createdByUserId": "uuid",
"createdAt": "2026-04-14T12:34:56.000Z"
}
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 }
Metrics
GET /api/metrics
Requires auth and an admin user. Returns lightweight in-memory process and request counters for the current backend process.
Response 200:
{
"startedAt": "2026-05-01T00:00:00.000Z",
"uptimeSeconds": 120,
"requests": {
"total": 10,
"inFlight": 0,
"errors": 0,
"averageDurationMs": 12.5,
"byStatus": { "2xx": 10 },
"byRoute": { "GET /api/health": 10 }
},
"memory": {
"rss": 0,
"heapTotal": 0,
"heapUsed": 0,
"external": 0,
"arrayBuffers": 0
},
"queues": {
"birdMilestoneReminders": {
"waiting": 0,
"active": 0,
"delayed": 0,
"completed": 0,
"failed": 0
}
}
}
Admin
PATCH /api/admin/rescue-workspaces/:workspaceId
Requires an admin user. Browser session tokens and admin-owned read_write integration tokens are accepted, so automation tools can approve or reject rescue claims after external validation.
Request body:
{
"rescueVerificationStatus": "approved"
}
Allowed statuses are pending, approved, and rejected.
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
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,household_macaw, orhousehold_hyacinth_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",
"vetClinicName": "Avian Care Center",
"vetClinicAddress": "123 Feather Lane, Raleigh, NC",
"vetAccountNumber": "FP-1001",
"vetDoctorName": "Dr. Rivera",
"gender": "female",
"dateOfBirth": "2023-05-10",
"gotchaDay": "2023-08-21",
"chartColor": "#cb3a35",
"photoDataUrl": "",
"notifyOnDob": false,
"notifyOnGotchaDay": true
}
Notes:
dateOfBirth,gotchaDay,photoDataUrl, and veterinary info fields may 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. Transfers a bird from the active flock to a flock owned by the provided receiving owner email. The sender does not need access to the receiving flock.
Request body:
{
"destinationOwnerEmail": "new-owner@example.com"
}
Notes:
- if the receiving owner email does not currently own a receiving flock, FlockPal creates a pending transfer, sends a bird-transfer invite, and returns
202; the bird stays in the sender's flock until the recipient signs in - pending transfers auto-complete when the recipient signs in; FlockPal creates or uses the recipient's personal flock as the receiving flock
- immediate transfer requires the receiving owner email to match an accepted
ownerof exactly one other flock - when transferred, the bird keeps its existing weight and vet history because the record itself is reassigned
Response 200:
{
"bird": {},
"destinationOwnerEmail": "new-owner@example.com",
"destinationWorkspace": {}
}
Response 202 when the receiving email does not currently own a receiving flock:
{
"ok": true,
"bird": {},
"destinationOwnerEmail": "new-owner@example.com",
"inviteSent": true,
"invitePreviewUrl": "http://localhost:5000/api/auth/magic-link/verify?token=...",
"inviteDelivery": "preview",
"message": "A bird transfer invite was sent. The bird will stay in this flock until the recipient signs in, then FlockPal will automatically move it to their receiving flock."
}
Possible errors:
400if the payload is invalid404if the bird is not in the active flock409if that owner email owns more than one receiving 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 to1through425, 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
Medications
GET /api/birds/:birdId/medications
Requires auth. Lists medication records for a bird in the active workspace.
Response 200:
{
"medications": []
}
POST /api/birds/:birdId/medications
Requires auth with write access and role owner, assistant, or caregiver. Creates a medication record.
Request body:
{
"name": "Meloxicam",
"dosage": "0.05 mL",
"frequency": "twice_daily",
"doseSchedule": [
{
"key": "dose-1",
"label": "Morning",
"time": "08:00"
},
{
"key": "dose-2",
"label": "Evening",
"time": "20:00"
}
],
"route": "Oral",
"startDate": "2026-04-14",
"endDate": "",
"notes": "Give with food"
}
Response 201:
{
"medication": {}
}
PUT /api/birds/:birdId/medications/:medicationId
Requires auth with write access and role owner, assistant, or caregiver. Updates a medication record.
DELETE /api/birds/:birdId/medications/:medicationId
Requires auth with write access and role owner, assistant, or caregiver. Deletes a medication record.
GET /api/birds/:birdId/medication-administrations
Requires auth. Lists medication administration events for a bird in the active workspace.
Response 200:
{
"administrations": []
}
POST /api/birds/:birdId/medications/:medicationId/administrations
Requires auth with write access and role owner, assistant, or caregiver. Upserts one scheduled medication event for a date and interval slot.
Request body:
{
"administeredOn": "2026-04-14",
"administrationSlot": "dose-1",
"status": "administered",
"notes": ""
}
status is administered or missed. administrationSlot identifies the interval event for that day, such as dose-1 or dose-2.
Medication frequency is one of once_daily, twice_daily, every_8_hours, every_6_hours, or as_needed. doseSchedule stores the editable labels and optional 24-hour HH:MM times used by administration slots.
Possible errors:
400ifendDateis beforestartDate404if the bird or medication 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'