# Patient Survey Engine API

Base URL:

- Dev: `http://localhost:3400`
- Production: `https://pse.mahoosuc.solutions`

Auth model:

- Public routes: health, config, Twilio webhooks, QR landing, patient intake
- Authenticated routes: all `/api/**`
- Roles:
  - `admin`
  - `operator`
  - `front_desk`

JWT:

- Send `Authorization: Bearer <token>`
- `front_desk` users are additionally scoped to a single `locationId`

## Health and Config

### `GET /health`
Returns health status, DB status, Twilio mode, and queue depth.

### `GET /api/config`
Returns runtime config used by the frontend.

## Auth

### `POST /api/auth/login`
Body:
```json
{
  "username": "admin",
  "password": "secret"
}
```

Response:
```json
{
  "token": "jwt",
  "expiresIn": "4h",
  "username": "admin",
  "role": "admin",
  "orgId": "default",
  "orgName": "Mahoosuc Solutions (Default)",
  "locationId": null
}
```

### `POST /api/auth/register`
Admin only.

Body:
```json
{
  "username": "frontdesk_1",
  "password": "secret",
  "orgId": "default",
  "role": "front_desk",
  "locationId": "location-uuid"
}
```

Notes:

- `role` must be `admin`, `operator`, or `front_desk`
- `locationId` is required for `front_desk`

### `POST /api/auth/logout`
Authenticated.

### `GET /api/auth/me`
Authenticated.

## Public QR Check-In

### `GET /check-in/:token`
Returns the patient/staff landing HTML page.

### `GET /check-in/:token/staff`
Returns the front-desk HTML page.

### `GET /check-in/:token/data`
Returns resolved location context.

Response:
```json
{
  "location": {
    "id": "uuid",
    "name": "QR Test Clinic",
    "address": "1 Main St",
    "orgName": "Mahoosuc Solutions (Default)",
    "phone": null
  },
  "today": "2026-04-02",
  "staffPath": "/check-in/<token>/staff"
}
```

### `POST /check-in/:token/patient/start`
Matches a patient against imported roster rows for the service date and creates a new intake session.

Body:
```json
{
  "name": "Jane Doe",
  "phone": "(555) 111-2222",
  "serviceDate": "2026-04-02"
}
```

Success response:
```json
{
  "success": true,
  "redirectUrl": "/intake/<session-token>",
  "sessionId": "uuid",
  "patientId": "uuid",
  "locationName": "QR Test Clinic"
}
```

Failure response when no matching roster row exists:
```json
{
  "error": "No scheduled patient match found for today.",
  "message": "Ask the front desk to upload the current roster or verify your phone number."
}
```

## Patient Intake

### `GET /intake/:token`
Returns intake HTML.

### `GET /intake/:token/data`
Returns questionnaire JSON for a valid token.

### `PUT /intake/:token/answer`
Saves a single answer.

Body:
```json
{
  "questionId": "qn-123",
  "answer": "Yes"
}
```

### `GET /intake/:token/progress`
Returns saved answers.

### `POST /intake/:token/submit`
Submits all answers.

Body:
```json
{
  "answers": [
    { "questionId": "qn-1", "answer": "No" }
  ]
}
```

## Organizations and Locations

### `GET /api/organizations`
List organizations visible to the caller.

### `GET /api/organizations/:id`
Organization detail plus summary stats.

### `POST /api/organizations`
Create organization.

### `PATCH /api/organizations/:id`
Update organization name, contact email, settings, or active flag.

### `POST /api/organizations/:id/onboard`
Bulk-create locations during onboarding.

### `GET /api/organizations/:id/locations`
List active locations for an org.

### `POST /api/organizations/:id/locations`
Create a location.

Body:
```json
{
  "name": "QR Test Clinic",
  "address": "1 Main St",
  "contactName": "Desk User",
  "contactEmail": "desk@example.com"
}
```

### `GET /api/organizations/:orgId/locations/:locId/qr`
Returns QR metadata.

Response:
```json
{
  "locationId": "uuid",
  "locationName": "QR Test Clinic",
  "publicToken": "hex-token",
  "landingUrl": "http://localhost:3400/check-in/<token>",
  "staffUrl": "http://localhost:3400/check-in/<token>/staff"
}
```

### `POST /api/organizations/:orgId/locations/:locId/qr/rotate`
Rotates the public QR token.

### `DELETE /api/organizations/:orgId/locations/:locId`
Soft-deactivates a location.

### `PUT /api/organizations/:id/twilio`
Set org-specific Twilio credentials.

### `GET /api/organizations/:id/twilio`
Read Twilio config status.

### `DELETE /api/organizations/:id/twilio`
Remove org-specific Twilio config.

## Front-Desk Rosters

Access:

- `admin`, `operator`, `front_desk`
- `front_desk` is location-scoped

### `GET /api/rosters/template`
Returns CSV template:
`patient_name,mobile_phone,service_date,last_visit_date,appointment_id`

### `POST /api/rosters/import?validate=true`
Validate only.

### `POST /api/rosters/import`
Imports a roster CSV for a date range.

Content type:

- `multipart/form-data`

Fields:

- `file`
- `locationId`
- `dateStart`
- `dateEnd`

Success response:
```json
{
  "success": true,
  "dryRun": false,
  "importId": "uuid",
  "imported": 1,
  "duplicates": 0,
  "filteredOut": 0,
  "errors": [],
  "total": 1,
  "locationName": "QR Test Clinic"
}
```

### `GET /api/rosters?locationId=<id>&dateStart=<YYYY-MM-DD>&dateEnd=<YYYY-MM-DD>`
List imports for a location.

### `GET /api/rosters/rows?locationId=<id>&serviceDate=<YYYY-MM-DD>`
List roster rows for a location and service date.

### `GET /api/rosters/:importId`
Import detail plus rows.

## Patient Import

Access:

- `admin`, `operator`

### `GET /api/import/template`
Returns patient import CSV template.

### `GET /api/import/sample`
Returns the sample patient CSV file.

### `POST /api/import?validate=true`
Validate patient import only.

### `POST /api/import`
Imports patients into the master patient table.

CSV columns:

- `patient_name`
- `mobile_phone`
- `last_visit_date`
- `location`

### `GET /api/patients`
Query:

- `status`
- `location`
- `limit`
- `offset`

### `GET /api/patients/stats`
Returns grouped status counts and location list.

## Dispatch

Access:

- `admin`, `operator`

### `POST /api/dispatch`
Queues all eligible `not_sent` patients.

Response:
```json
{
  "success": true,
  "queued": 14,
  "batchSize": 10,
  "totalBatches": 2,
  "estimatedMinutes": 1,
  "message": "14 patients queued for dispatch. Estimated completion: ~1 minutes."
}
```

### `GET /api/dispatch/status`
Returns queue progress.

### `POST /api/dispatch/pause`
Pause queued dispatches.

### `POST /api/dispatch/resume`
Resume paused dispatches.

### `GET /api/dispatch/log`
Query:

- `limit`
- `event`

### `POST /api/dispatch/:patientId`
Queues one patient.

## Responses and Exports

Access:

- `admin`, `operator`

### `GET /api/responses`
List completed responses grouped by section.

### `GET /api/responses/:sessionId/fhir`
FHIR `QuestionnaireResponse`.

### `GET /api/responses/:sessionId/ccd`
CCD XML export.

### `GET /api/responses/:sessionId/text`
Plain-text export.

### `GET /api/responses/analytics/summary`
Aggregate answer counts by question.

### `GET /api/responses/analytics/by-location`
Session counts by location.

### `GET /api/responses/analytics/sdoh-risk`
Risk scoring by location.

### `GET /api/responses/analytics/trends?interval=day|week|month`
Completion trends.

### `GET /api/responses/export/fhir-bundle`
Optional query:

- `location`

### `GET /api/responses/export/csv`
Optional query:

- `location`

## Provider Delivery

Access:

- `admin`, `operator`

### `GET /api/providers`
List provider endpoints.

### `POST /api/providers`
Registers a provider endpoint.

Body:
```json
{
  "name": "Mock Receiver",
  "locationPattern": "QR Test Clinic",
  "deliveryFormat": "fhir",
  "endpointUrl": "https://example.com/receiver",
  "authHeader": "Bearer abc"
}
```

### `POST /api/providers/deliver/:sessionId`
Delivers a completed response to matching endpoints.

### `GET /api/providers/deliveries/:sessionId`
Delivery history for a session.

### `POST /api/providers/deliveries/:deliveryId/ack`
Marks a delivery as acknowledged.

### `DELETE /api/providers/:id`
Deletes a provider endpoint.

## Questionnaires

Access:

- `admin`, `operator`

### `GET /api/questionnaires`
### `GET /api/questionnaires/:id`
### `POST /api/questionnaires`
### `POST /api/questionnaires/:id/questions`
### `PUT /api/questionnaires/:id/questions/:qid`
### `DELETE /api/questionnaires/:id/questions/:qid`

## Campaigns

Access:

- `admin`, `operator`

### `GET /api/campaigns`
### `GET /api/campaigns/:id`
### `POST /api/campaigns`
### `POST /api/campaigns/:id/launch`
### `GET /api/campaigns/:id/analytics`

## Phone Numbers

Access:

- `admin`, `operator`

### `GET /api/phone-numbers`
### `GET /api/phone-numbers/available?type=local|tollfree&areaCode=212`
### `POST /api/phone-numbers/provision`
### `DELETE /api/phone-numbers/:id`
### `GET /api/phone-numbers/costs`

## Admin Utilities

Access:

- `admin`, `operator`

### `DELETE /api/admin/test-data`
Deletes `not_sent` patients and dependent rows for the current org.

### `POST /api/admin/cleanup?days=90`
Deletes old expired sessions, logs, and queue rows.

### `POST /api/admin/reset`
Mock mode only. Clears org data.

### `GET /api/admin/pipeline`
Returns consolidated dashboard stats.

## Twilio Webhooks

### `POST /webhooks/twilio/status`
In dev mode this can be called directly from Postman.

Body fields commonly used:

- `MessageSid`
- `MessageStatus`
- `To`
- `ErrorCode`

### `POST /webhooks/twilio/inbound`
In dev mode this can be called directly from Postman.

Body fields commonly used:

- `From`
- `Body`
- `MessageSid`

Supported inbound commands:

- `STOP`
- `HELP`

## Validation Notes

- `GET /intake/:token` returns HTML, not JSON
- Use `GET /intake/:token/data` for JSON intake validation
- `POST /api/dispatch` queues work; it does not synchronously return final Twilio delivery results
- In dev mode, Twilio webhooks skip signature validation
- `front_desk` users can access roster routes only for their assigned `locationId`
