# 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: ```http Authorization: Bearer ``` 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: ```text 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-tokens` - `POST /api/integration-tokens` - `DELETE /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_only` or `read_write` Use them exactly like session tokens: ```http 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: ```json { "error": "Authentication required." } ``` Browser-session-only endpoints reject integration tokens with: ```json { "error": "This endpoint requires a browser session instead of an integration token." } ``` Write endpoints reject read-only integration tokens with: ```json { "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 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: ```text 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: ```text 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: ```http 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: - `owner` - `assistant` - `caregiver` - `viewer` Role requirements are called out per endpoint below. If the signed-in member lacks permission, the API returns: ```json { "error": "You do not have permission for that action." } ``` ## Data Shapes ### User ```json { "id": "uuid", "email": "person@example.com", "name": "Taylor", "createdAt": "2026-04-14T12:34:56.000Z" } ``` ### Workspace ```json { "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 ```json { "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 ```json { "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 ```json { "id": "uuid", "birdId": "uuid", "weightGrams": 92, "recordedOn": "2026-04-14", "notes": "Morning check" } ``` ### Vet Visit ```json { "id": "uuid", "birdId": "uuid", "visitedOn": "2026-04-14", "clinicName": "Avian Care Center", "reason": "Wellness exam", "notes": "Healthy" } ``` ### Medication ```json { "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 ```json { "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` - `workspaceType` is `standard` or `rescue` - member `role` is `owner`, `assistant`, `caregiver`, or `viewer` - bird `gender` is `unknown`, `male`, or `female` - 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: ```json { "error": "Invalid ... payload", "details": {} } ``` ## Endpoints ### Health #### `GET /api/health` Public health check. Response `200`: ```json { "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`: ```json { "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: ```json { "rescueVerificationStatus": "approved" } ``` Allowed statuses are `pending`, `approved`, and `rejected`. ### Authentication #### `GET /api/auth/providers` Public list of configured OAuth providers. Response `200`: ```json { "providers": [ { "providerKey": "google", "displayName": "Google", "enabled": true } ] } ``` #### `POST /api/auth/register` Password registration is disabled. Response `410`: ```json { "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`: ```json { "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: ```json { "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`: ```json { "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: ```bash 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: ```json { "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 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`: ```json { "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: ```bash 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: ```json { "workspaceId": 1002 } ``` Response `200` returns the same shape as `GET /api/auth/session`. `curl` example: ```bash 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: ```text 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: ```bash 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 ### Workspaces #### `GET /api/workspaces` Requires a browser session. Lists the signed-in user's workspace memberships. Response `200`: ```json { "workspaces": [] } ``` #### `POST /api/workspaces` Requires a browser session. Creates a new workspace and makes the current user its `owner`. Request body: ```json { "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`, `household_macaw`, or `household_hyacinth_macaw` - rescue workspaces are forced to `rescue_free` Response `201`: ```json { "workspace": {} } ``` #### `GET /api/workspace` Requires auth. Returns the active workspace. Browser sessions and integration tokens can both use this endpoint. Response `200`: ```json { "workspace": {} } ``` #### `PUT /api/workspace` Requires auth with write access and role `owner` or `assistant`. Updates the active workspace. Request body: ```json { "name": "Updated Flock", "workspaceType": "standard", "billingEmail": "billing@example.com", "billingPlan": "household_basic" } ``` Response `200`: ```json { "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`: ```json { "deletedWorkspaceId": 1001, "token": "raw-session-token", "session": {} } ``` Possible errors: - `409` if birds are still assigned to the flock - `404` if 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`: ```json { "members": [] } ``` #### `POST /api/workspace/members` Requires auth with write access and role `owner` or `assistant`. Invites or upserts a workspace member. Request body: ```json { "name": "Alex", "email": "alex@example.com", "role": "viewer" } ``` Response `201`: ```json { "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: - `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`: ```json { "birds": [] } ``` #### `POST /api/birds` Requires auth with write access and role `owner`, `assistant`, or `caregiver`. Creates a bird. Request body: ```json { "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 strings - `chartColor` defaults to `#cb3a35` Response `201`: ```json { "bird": {} } ``` Possible errors: - `409` if the workspace already uses that `tagId` #### `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`: ```json { "bird": {} } ``` Possible errors: - `404` if the bird does not exist in the active workspace - `409` if the workspace already uses that `tagId` #### `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: ```json { "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 `owner` of exactly one other flock - when transferred, the bird keeps its existing weight and vet history because the record itself is reassigned Response `200`: ```json { "bird": {}, "destinationOwnerEmail": "new-owner@example.com", "destinationWorkspace": {} } ``` Response `202` when the receiving email does not currently own a receiving flock: ```json { "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: - `400` if the payload is invalid - `404` if the bird is not in the active flock - `409` if that owner email owns more than one receiving flock - `409` if the destination flock already has a bird using the same `tagId` #### `POST /api/birds/:birdId/transfer-code` Requires a browser session, write access, and role `owner` or `assistant`. Creates a unique transfer code for a bird. Creating a new open code for the same bird revokes earlier unused codes for that bird. Response `201`: ```json { "transferCode": { "code": "secure-code", "bird": {} } } ``` #### `POST /api/bird-transfer-codes/:code/accept` Requires a browser session, write access, and role `owner` or `assistant`. Accepts a transfer code into the signed-in user's active flock. Response `200`: ```json { "bird": {}, "sourceWorkspaceName": "Previous Flock", "workspace": {} } ``` Possible errors: - `404` if the code does not exist, was revoked, was already used, or the bird is no longer available - `409` if the bird is already in the active flock or the active flock already has the same `tagId` #### `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: - `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 `425`, default `30` Response `200`: ```json { "weights": [] } ``` #### `POST /api/birds/:birdId/weights` Requires auth with write access and role `owner`, `assistant`, or `caregiver`. Creates a weight entry. Request body: ```json { "weightGrams": 92, "recordedOn": "2026-04-14", "notes": "Morning check" } ``` Response `201`: ```json { "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`: ```json { "vetVisits": [] } ``` #### `POST /api/birds/:birdId/vet-visits` Requires auth with write access and role `owner`, `assistant`, or `caregiver`. Creates a vet visit. Request body: ```json { "visitedOn": "2026-04-14", "clinicName": "Avian Care Center", "reason": "Wellness exam", "notes": "Healthy" } ``` Response `201`: ```json { "vetVisit": {} } ``` Possible errors: - `404` if 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`: ```json { "medications": [] } ``` #### `POST /api/birds/:birdId/medications` Requires auth with write access and role `owner`, `assistant`, or `caregiver`. Creates a medication record. Request body: ```json { "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`: ```json { "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`: ```json { "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: ```json { "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: - `400` if `endDate` is before `startDate` - `404` if 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`: ```json { "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: ```json { "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`: ```json { "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: ```bash 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: ```http 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: ```bash 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" }' ``` 2. Copy the `previewUrl` from the response if local email is not configured. 3. Open that URL in a browser and capture `auth_token` from the frontend redirect URL. 4. Use the token: ```bash curl http://localhost:5000/api/auth/session \ -H 'Authorization: Bearer YOUR_SESSION_TOKEN' ```