# 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/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'
```
