Files
FlockPal/docs/API_REFERENCE.md
T
2026-04-14 23:13:17 -04:00

988 lines
20 KiB
Markdown

# 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 <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:
```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/transfers/draft`
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`
- `manager`
- `staff`
- `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",
"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"
}
```
## 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:
```json
{
"error": "Invalid ... payload",
"details": {}
}
```
## Endpoints
### Health
#### `GET /api/health`
Public health check.
Response `200`:
```json
{ "ok": true }
```
### 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
### 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:
```json
{
"birdId": "uuid",
"destinationOwnerEmail": "new-owner@example.com",
"notes": "Optional draft note"
}
```
Response `201`:
```json
{
"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 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`, or `household_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 `manager`. Updates the active workspace.
Request body:
```json
{
"name": "Updated Flock",
"workspaceType": "standard",
"billingEmail": "billing@example.com",
"billingPlan": "household_basic"
}
```
Response `200`:
```json
{
"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`:
```json
{
"members": []
}
```
#### `POST /api/workspace/members`
Requires auth with write access and role `owner` or `manager`. 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 `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`:
```json
{
"birds": []
}
```
#### `POST /api/birds`
Requires auth with write access and role `owner`, `manager`, or `staff`. Creates a bird.
Request body:
```json
{
"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`:
```json
{
"bird": {}
}
```
Possible errors:
- `409` if the workspace already uses that `tagId`
#### `PUT /api/birds/:birdId`
Requires auth with write access and role `owner`, `manager`, or `staff`. 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`
#### `DELETE /api/birds/:birdId`
Requires auth with write access 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`:
```json
{
"weights": []
}
```
#### `POST /api/birds/:birdId/weights`
Requires auth with write access and role `owner`, `manager`, or `staff`. 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`, `manager`, or `staff`. 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
### 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'
```