pediatric-ai-scribe-v3/docs/api-reference.md
Daniel 67e416c6d9
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Android APK / Build signed APK (push) Successful in 2m38s
Forgejo Docker Build / Build Docker image (push) Successful in 12s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
docs: merge the duplicate pairs and correct them against the running app
Three pairs of docs described the same thing twice, and the copies had drifted
apart. Merged each into one file, keeping the unique content from both:

- ARCHITECTURE.md -> architecture.md (its operational map: ownership, request
  flow, runtime boundaries, source of truth, deployment shape)
- DEVELOPMENT.md -> developer-guide.md (change workflow, Clinical Assistant
  high-risk areas, frontend rendering rules, deployment checks)
- transcription-options.md -> speech.md (the clinic setup table, and the list
  of browser-Whisper paths that must stay removed)

Then audited what remained against the code and the live database rather than
against the previous docs. Corrected:

- Google Vertex was still documented as a provider across nine files. The SDK
  is gone; AI_PROVIDER=vertex now logs an advisory and falls back to
  OpenRouter, and Gemini is reached through LiteLLM. Fixed the provider
  selection order to match src/utils/ai.js, which starts from LITELLM_API_BASE.
- promptSafe was documented on 8 routes; it is on 13.
- Node 20 -> 24, "24 vanilla JS modules" -> no fixed count, and
  transcribe.js/tts.js -> sttProvider.js/ttsProvider.js, which is what exists.
- STT/TTS are LiteLLM-only; README listed direct Google, AWS Transcribe and
  ElevenLabs paths that are not in the runtime.
- Learning Hub PPTX export was documented as pptxgenjs, which is not a
  dependency. It is pandoc against a reference deck.
- POST /api/admin/milestones/seed does not exist; it is /bulk-import.
- NEXTCLOUD_URL and NTFY_TOPIC are not read anywhere. Nextcloud is per-user in
  the users table, and the ntfy topic is derived as pedscribe-{userId}.
- A prose paragraph sat inside the Clinical Assistant settings table, so half
  the rows rendered as text.

Filled the gaps the audit exposed:

- database.md was missing 12 of 29 tables, including user_resources,
  personal_notes, login_codes, registration_invites and generated_image_jobs.
- developer-guide.md was missing 11 routers and 10 frontend modules.
- api-reference.md detailed 121 of 244 endpoints and said so, but whole
  features were absent. Added an endpoint index covering Clinical Assistant,
  My Resources, Notes, Diagrams, ED Encounters, invites and sign-in codes.
- configuration.md was missing METRICS_TOKEN, REDIS_URL, API_RATE_LIMIT_MAX,
  the LITELLM_* model variables, the DB_* ones maintenance.js reads, and the
  per-purpose S3 resolution scheme.
- clinical-assistant.md documented 2 of its 17 environment variables.
- features-explained.md had no entry for My Resources or Clinical Assistant.

Renamed the three remaining SHOUTING filenames to kebab-case, which is what the
docs viewer's prettyName() was working around, and rewrote README's index,
which listed architecture.md twice and omitted nine files.

Noted but not changed: the Turnstile site key is hardcoded in index.html rather
than read from TURNSTILE_SITE_KEY, and /api/health/detailed can report
tts: 'elevenlabs' though no ElevenLabs path exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 04:57:35 +02:00

53 KiB

API Reference

Working endpoint reference for the main PedAI flows. It covers the clinical, auth, Learning Hub, user data, and admin surfaces most commonly used by the frontend, but the source of truth is still server.js plus src/routes/*.js. Unless noted otherwise, authenticated endpoints require a valid web cookie or Authorization: Bearer <token> header.


Table of Contents


Authentication

Base path: /api/auth/

POST /api/auth/register

Register a new user account. The first user to register is automatically assigned the admin role. Turnstile is required only when TURNSTILE_SECRET_KEY is configured. If SMTP is configured, the response asks the user to verify email; without SMTP, the account is auto-verified and logged in.

  • Auth required: No
  • Request body:
    {
      "name": "string",
      "email": "string",
      "password": "string",
      "turnstileToken": "string"
    }
    
  • Response:
    {
      "success": true,
      "needsVerification": true,
      "message": "Check your email for verification link."
    }
    

GET /api/auth/verify-email

Verify a user's email address via the link sent during registration.

  • Auth required: No
  • Query parameters:
    Parameter Type Description
    token string Email verification token from the link
  • Response: HTML status page for success or expired/invalid token.

POST /api/auth/resend-verification

Resend the email verification link.

  • Auth required: No
  • Request body:
    {
      "email": "string"
    }
    
  • Response:
    {
      "success": true,
      "message": "Verification email sent."
    }
    

POST /api/auth/login

Authenticate a user. Supports optional TOTP two-factor authentication. On success, sets an HTTP-only JWT cookie and returns user data.

  • Auth required: No
  • Request body:
    {
      "email": "string",
      "password": "string",
      "totpCode": "string (optional, required if 2FA is enabled)"
    }
    
  • Response:
    {
      "success": true,
      "token": "jwt-string",
      "sessionId": "string",
      "user": {
        "id": "number",
        "name": "string",
        "email": "string",
        "role": "admin | moderator | user",
        "totp_enabled": "boolean",
        "email_verified": "boolean"
      }
    }
    
    If 2FA is enabled but totpCode is not provided:
    {
      "requires2FA": true
    }
    

POST /api/auth/setup-2fa

Generate a TOTP secret and QR code for setting up two-factor authentication.

  • Auth required: Yes
  • Request body: None
  • Response:
    {
      "success": true,
      "secret": "string",
      "qrCode": "string (data URI)"
    }
    

POST /api/auth/verify-2fa

Verify a TOTP code and enable two-factor authentication for the user.

  • Auth required: Yes
  • Request body:
    {
      "code": "string"
    }
    
  • Response:
    {
      "success": true,
      "backupCodes": ["string"]
    }
    

POST /api/auth/disable-2fa

Disable two-factor authentication. Requires password confirmation.

  • Auth required: Yes
  • Request body:
    {
      "password": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

POST /api/auth/forgot-password

Initiate password reset. Sends a reset link to the user's email.

  • Auth required: No
  • Request body:
    {
      "email": "string",
      "turnstileToken": "string"
    }
    
  • Response:
    {
      "success": true,
      "message": "If account exists, reset email sent"
    }
    

POST /api/auth/reset-password

Reset the user's password using a valid reset token.

  • Auth required: No
  • Request body:
    {
      "token": "string",
      "newPassword": "string"
    }
    
  • Response:
    {
      "success": true,
      "passwordWarning": "string (optional)"
    }
    

POST /api/auth/logout

Log out the current user by deleting the current session row when a token is present and clearing the auth cookie.

  • Auth required: No
  • Request body: None
  • Response:
    {
      "success": true
    }
    

GET /api/auth/me

Retrieve the currently authenticated user's profile.

  • Auth required: Yes
  • Response:
    {
      "user": {
        "id": "number",
        "name": "string",
        "email": "string",
        "role": "admin | moderator | user",
        "totp_enabled": "boolean",
        "email_verified": "boolean",
        "canLocalAuth": "boolean"
      }
    }
    

GET /api/auth/registration-status

Check whether new user registration is enabled on this instance.

  • Auth required: No
  • Response:
    {
      "registrationEnabled": true
    }
    

OIDC / SSO

Base path: /api/auth/

GET /api/auth/oidc-status

Check whether OIDC single sign-on is configured and enabled.

  • Auth required: No
  • Response:
    {
      "oidcEnabled": true,
      "disableLocalAuth": false,
      "buttonLabel": "Sign in with SSO"
    }
    

GET /api/auth/oidc

Initiates the OIDC login flow. Redirects the user to the configured identity provider.

  • Auth required: No
  • Response: HTTP 302 redirect to the IdP authorization endpoint.

GET /api/auth/oidc/callback

Handles the callback from the identity provider after authentication. Processes the authorization code and creates/logs in the user.

  • Auth required: No
  • Query parameters: Standard OIDC callback parameters (code, state).
  • Response: Redirects to the application with a session cookie set.

AI Generation

All generation endpoints require authentication and stream or return AI-generated clinical text. Each accepts a model parameter to select the LLM.

POST /api/generate-hpi-encounter

Generate a History of Present Illness note from a patient encounter transcript.

  • Auth required: Yes
  • Request body:
    {
      "transcript": "string",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string",
      "setting": "string",
      "physicianMemories": "string (optional)"
    }
    
  • Response: JSON with success, generated hpi, and resolved model.

POST /api/generate-hpi-dictation

Generate an HPI note from a physician's dictated summary (same parameters as encounter).

  • Auth required: Yes
  • Request body:
    {
      "transcript": "string",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string",
      "setting": "string",
      "physicianMemories": "string (optional)"
    }
    
  • Response: JSON with success, generated hpi, and resolved model.

POST /api/generate-soap

Generate a SOAP note from a patient encounter transcript.

  • Auth required: Yes
  • Request body:
    {
      "transcript": "string",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string",
      "type": "string",
      "additionalInstructions": "string (optional)",
      "physicianMemories": "string (optional)"
    }
    
  • Response: JSON with success, generated soap, and resolved model.

POST /api/generate-chart-review

Generate a chart review summary from clinical notes.

  • Auth required: Yes
  • Request body:
    {
      "notes": "string",
      "visitType": "string",
      "model": "string"
    }
    
  • Response: JSON with success, generated chart review content, and resolved model.

POST /api/generate-hospital-course

Generate a hospital course summary from clinical notes.

  • Auth required: Yes
  • Request body:
    {
      "notes": "string",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string",
      "format": "string",
      "physicianMemories": "string (optional)"
    }
    
  • Response: JSON with success, generated summary, and resolved model.

POST /api/well-visit/shadess

Generate a SHADESS (Strengths, Home, Activities, Drugs, Emotions, Sexuality, Safety) assessment from well-visit responses.

  • Auth required: Yes
  • Request body:
    {
      "responses": "object",
      "patientAge": "string",
      "model": "string"
    }
    
  • Response: JSON with success, generated assessment, and resolved model.

POST /api/sick-visit/note

Generate a sick visit note from chief complaint, transcript/dictation, ROS/PE, and diagnosis context.

  • Auth required: Yes
  • Request body:
    {
      "chiefComplaint": "string",
      "transcript": "string (optional)",
      "dictation": "string (optional)",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string",
      "ros": "string (optional)",
      "physicalExam": "string (optional)",
      "diagnoses": "string (optional)",
      "physicianMemories": "string (optional)"
    }
    
  • Response: JSON with success, generated note, and resolved model.

POST /api/patient-education

Generate a parent-facing education handout from an existing clinician note. The frontend exposes this as the Handout action beside generated notes.

  • Auth required: Yes
  • Request body:
    {
      "noteText": "string",
      "diagnosis": "string (optional)",
      "medications": "string (optional)",
      "patientAge": "string (optional)",
      "language": "string (optional, defaults to English)",
      "readingLevel": "string (optional)",
      "model": "string (optional)"
    }
    
  • Response:
    {
      "success": true,
      "handout": "plain-text parent handout",
      "model": "string"
    }
    

POST /api/generate-milestone-narrative

Generate a developmental milestone narrative from milestone data.

  • Auth required: Yes
  • Request body:
    {
      "milestones": "object | array",
      "patientAge": "string",
      "patientGender": "string",
      "model": "string"
    }
    
  • Response: JSON with success, generated narrative, and resolved model.

POST /api/generate-milestone-summary

Generate a concise summary from a previously generated milestone narrative.

  • Auth required: Yes
  • Request body:
    {
      "narrative": "string",
      "model": "string"
    }
    
  • Response: JSON with success, generated summary, and resolved model.

POST /api/refine

Refine existing clinical text according to provided instructions.

  • Auth required: Yes
  • Request body:
    {
      "currentDocument": "string",
      "instructions": "string",
      "sourceContext": "string (optional)",
      "model": "string"
    }
    
  • Response: JSON with success, refined text, and resolved model.

POST /api/shorten

Shorten a block of clinical text while preserving key information.

  • Auth required: Yes
  • Request body:
    {
      "document": "string",
      "model": "string"
    }
    
  • Response: JSON with success, shortened text, and resolved model.

POST /api/clarify

Improve clarity and readability of clinical text.

  • Auth required: Yes
  • Request body:
    {
      "document": "string",
      "context": "string (optional)",
      "model": "string"
    }
    
  • Response: JSON with success, clarified text, and resolved model.

Transcription

GET /api/transcribe/status

Check whether speech-to-text transcription is available and which provider is configured.

  • Auth required: Yes
  • Response:
    {
      "available": true,
      "provider": "litellm | none"
    }
    

POST /api/transcribe

Transcribe an audio file to text. Accepts multipart form data with the audio file.

  • Auth required: Yes
  • Content-Type: multipart/form-data
  • File size limit: 25 MB
  • Form fields:
    Field Type Description
    audio file Audio file to transcribe
    module string Optional. What produced the recording; recorded on the backup. Defaults to recording.
  • Response:
    {
      "success": true,
      "text": "string",
      "provider": "string",
      "duration": "number (seconds)",
      "backupId": "number | null"
    }
    
  • The audio is kept for 24 hours whether or not transcription succeeds, so this endpoint does not need a second upload to /api/audio-backups. backupId is null when the copy could not be stored — the transcription still returns, because losing the transcript would be worse than losing the copy.
  • The model is the caller's users.stt_model, then the stt.model setting, then LITELLM_STT_MODEL.

Text-to-Speech

POST /api/text-to-speech

Convert text to speech. Returns an audio stream.

  • Auth required: Yes
  • Request body:
    {
      "text": "string",
      "voice": "string (optional)"
    }
    
  • Response: Audio stream (audio/mpeg).
  • Response headers:
    Header Description
    X-TTS-Provider The TTS provider used for synthesis

Models

GET /api/models

List available AI models for the current instance.

  • Auth required: Yes
  • Response:
    {
      "provider": "string",
      "models": [
        {
          "id": "string",
          "name": "string",
          "cost": "string",
          "category": "string"
        }
      ],
      "defaultModel": "string"
    }
    

Encounters

GET /api/encounters/saved

List active, unexpired saved encounters for the authenticated user.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "encounters": [
        {
          "id": "number",
          "label": "string",
          "enc_type": "string",
          "status": "string",
          "created_at": "string (ISO 8601)",
          "updated_at": "string (ISO 8601)",
          "expires_at": "string (ISO 8601)",
          "transcript_preview": "string",
          "note_preview": "string"
        }
      ]
    }
    

GET /api/encounters/saved/:id

Retrieve a single saved encounter with full data.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Encounter ID
  • Response:
    {
      "success": true,
      "encounter": {
        "id": "number",
        "label": "string",
        "enc_type": "string",
        "transcript": "string",
        "generated_note": "string",
        "partial_data": "stringified JSON",
        "created_at": "string (ISO 8601)",
        "updated_at": "string (ISO 8601)"
      }
    }
    

POST /api/encounters/saved

Create or update a saved encounter. Uses idempotency_key to prevent duplicates on create and expected_version for optimistic locking on updates.

  • Auth required: Yes
  • Request body:
    {
      "id": "number (optional, update existing encounter)",
      "label": "string",
      "enc_type": "string",
      "transcript": "string",
      "generated_note": "string",
      "partial_data": "object (optional)",
      "status": "string (optional)",
      "idempotency_key": "string (optional)",
      "expected_version": "number (optional, update only)"
    }
    
  • Response:
    {
      "success": true,
      "id": "number",
      "version": "number (updates only)"
    }
    

DELETE /api/encounters/saved/:id

Delete a saved encounter.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Encounter ID
  • Response:
    {
      "success": true
    }
    

Memories

Physician memories are encrypted user templates and prompt preferences. Only AI-context categories are injected into generation prompts; custom and legacy correction_* rows are excluded from /api/memories/context.

GET /api/memories

List all memories for the authenticated user. Returns up to 200 entries.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "memories": [
        {
          "id": "number",
          "category": "string",
          "name": "string",
          "content": "string",
          "created_at": "string (ISO 8601)",
          "updated_at": "string (ISO 8601)"
        }
      ]
    }
    

POST /api/memories

Create a new memory.

  • Auth required: Yes
  • Request body:
    {
      "category": "string",
      "name": "string",
      "content": "string"
    }
    
  • Response:
    {
      "success": true,
      "id": "number",
      "originalSize": "number",
      "compressedSize": "number"
    }
    

PUT /api/memories/:id

Update an existing memory.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Memory ID
  • Request body:
    {
      "category": "string",
      "name": "string",
      "content": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

DELETE /api/memories/:id

Delete a memory.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Memory ID
  • Response:
    {
      "success": true
    }
    

GET /api/memories/context

Retrieve AI-context template/preference rows formatted for injection into AI generation prompts. custom rows and legacy correction_* rows are excluded.

  • Auth required: Yes
  • Response:
    {
        "success": true,
        "context": "string"
    }
    

Audio Backups

Encrypted 24-hour storage for recordings. Every recording made through /api/transcribe is kept automatically; this group is for browser-held copies (saved when the server could not be reached at all), and for listing, downloading and deleting.

Payload lives in object storage when AUDIO_BACKUPS_S3_* is configured and in the audio_backups column otherwise; metadata is always in Postgres, so these endpoints behave identically either way. See docs/speech.md.

POST /api/audio-backups

Upload an audio backup. The file is gzip-compressed and encrypted on the server.

  • Auth required: Yes
  • Content-Type: multipart/form-data
  • Form fields:
    Field Type Description
    audio file Audio file to back up
  • Response:
    {
      "success": true,
      "id": "number"
    }
    

GET /api/audio-backups

List unexpired audio backups for the authenticated user.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "backups": [
        {
          "id": "number",
          "module": "string",
          "mime_type": "string",
          "size_bytes": "number",
          "compressed_bytes": "number",
          "created_at": "string (ISO 8601)",
          "expires_at": "string (ISO 8601)"
        }
      ]
    }
    

GET /api/audio-backups/:id/audio

Download a decompressed audio backup.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Audio backup ID
  • Response: Binary audio stream with appropriate content-type header.

DELETE /api/audio-backups/:id

Delete an audio backup.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Audio backup ID
  • Response:
    {
      "success": true
    }
    

Documents

S3-backed document storage for clinical files.

GET /api/documents

List all documents for the authenticated user.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "s3_configured": true,
      "documents": [
        {
          "id": "number",
          "filename": "string",
          "mime_type": "string",
          "size_bytes": "number",
          "description": "string",
          "created_at": "string (ISO 8601)"
        }
      ]
    }
    

POST /api/documents/upload

Upload a document to S3 storage.

  • Auth required: Yes
  • Content-Type: multipart/form-data
  • File size limit: 10 MB
  • Allowed types: PDF, images (JPEG, PNG, GIF), Word documents (.doc, .docx), plain text (.txt), CSV (.csv)
  • Form fields:
    Field Type Description
    file file Document to upload
  • Response:
    {
      "success": true,
      "id": "number",
      "filename": "string"
    }
    

GET /api/documents/:id/download

Download a document.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Document ID
  • Response: JSON containing a short-lived pre-signed download URL.
    {
      "success": true,
      "url": "string"
    }
    

DELETE /api/documents/:id

Delete a document from S3 storage.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Document ID
  • Response:
    {
      "success": true
    }
    

User Preferences

GET /api/user/preferences

Get the authenticated user's preferences.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "stt_model": "string",
      "tts_voice": "string"
    }
    

POST /api/user/preferences

Save user preferences.

  • Auth required: Yes
  • Request body:
    {
      "stt_model": "string",
      "tts_voice": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

GET /api/user/preferences/options

List available STT models and TTS voices that the user can choose from.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "sttModels": [
        { "value": "string", "label": "string" }
      ],
      "ttsVoices": [
        { "value": "string", "label": "string" }
      ],
      "sttProvider": "string",
      "ttsProvider": "string"
    }
    

POST /api/user/webdav-path

Save the user's preferred WebDAV learning content path.

  • Auth required: Yes
  • Request body:
    {
      "path": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

Phone Extensions And Pagers

Base path: /api/extensions. All endpoints require authentication and operate on the current user's personal directory.

Portable import/export fields are location, name, number, type, and notes. Exports intentionally omit database IDs and user IDs. type is either extension or pager.

GET /api/extensions

List active or trashed phone directory entries.

  • Auth required: Yes
  • Query parameters:
    Parameter Type Description
    trash string 1 or true returns trashed entries instead of active entries.
    q string Case-insensitive search over location, name, number, and notes.
  • Response:
    {
      "success": true,
      "items": [
        {
          "id": 1,
          "location": "ED",
          "name": "Charge Nurse",
          "number": "1234",
          "type": "extension",
          "notes": "Nights",
          "trashed_at": null,
          "created_at": "2026-05-08T00:00:00.000Z",
          "updated_at": "2026-05-08T00:00:00.000Z"
        }
      ]
    }
    

POST /api/extensions

Create a phone extension or pager entry.

  • Auth required: Yes
  • Request body:
    {
      "location": "ED",
      "name": "Charge Nurse",
      "number": "1234",
      "type": "extension",
      "notes": "Optional note"
    }
    
  • Response:
    {
      "success": true,
      "id": 1
    }
    

PUT /api/extensions/:id

Update an existing entry owned by the current user.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    id number Entry ID.
  • Request body: Same fields as POST /api/extensions.
  • Response:
    {
      "success": true
    }
    

DELETE /api/extensions/:id

Move an active entry to trash. This is a soft delete.

  • Auth required: Yes
  • Response:
    {
      "success": true
    }
    

POST /api/extensions/:id/restore

Restore a trashed entry.

  • Auth required: Yes
  • Response:
    {
      "success": true
    }
    

DELETE /api/extensions/:id/purge

Permanently delete a trashed entry.

  • Auth required: Yes
  • Response:
    {
      "success": true
    }
    

GET /api/extensions/export

Export active entries as pedscribe-extensions.zip. The ZIP contains pedscribe-extensions.json.

  • Auth required: Yes
  • Response headers:
    Header Description
    Content-Type application/zip
    Content-Disposition Attachment filename.
    X-Export-Count Number of exported active entries.
  • JSON file shape inside ZIP:
    {
      "format": "pedscribe-phone-directory",
      "version": 1,
      "exported_at": "2026-05-08T00:00:00.000Z",
      "items": [
        {
          "location": "ED",
          "name": "Charge Nurse",
          "number": "1234",
          "type": "extension",
          "notes": "Optional note"
        }
      ]
    }
    

POST /api/extensions/import

Import entries from a JSON request body. Exact active duplicates are skipped. Exact trashed matches can be restored when requested. Possible duplicates are identified by loose matching (location + number or number + type) and skipped unless explicitly imported.

  • Auth required: Yes
  • Request body: Export payload object, an array of entry objects, or an object with items and options.
    {
      "items": [
        {
          "location": "ED",
          "name": "Charge Nurse",
          "number": "1234",
          "type": "extension",
          "notes": "Optional note"
        }
      ],
      "options": {
        "restoreTrashed": true,
        "importPossibleDuplicates": false
      }
    }
    
  • Response:
    {
      "success": true,
      "imported": 2,
      "restored": 1,
      "skipped": 1,
      "possibleSkipped": 1,
      "total": 3
    }
    

POST /api/extensions/import/preview

Preview a JSON import before committing it. The preview categorizes entries as new, exact active duplicates, exact trashed matches, or possible duplicates.

  • Auth required: Yes
  • Request body: Same JSON shapes accepted by POST /api/extensions/import.
  • Response:
    {
      "success": true,
      "preview": {
        "summary": {
          "total": 3,
          "new": 1,
          "exactActive": 1,
          "exactTrashed": 0,
          "possible": 1
        },
        "entries": []
      }
    }
    

POST /api/extensions/import-file

Import entries from a multipart file upload. Accepts a PedScribe JSON export or a ZIP export containing JSON. Form fields restoreTrashed=true and importPossibleDuplicates=true control the same duplicate handling options as JSON import.

  • Auth required: Yes
  • Request body: multipart/form-data with field file.
  • Limits: Maximum upload size is 1 MB. At most 1000 valid entries are processed.
  • Response: Same as POST /api/extensions/import.

POST /api/extensions/import-file/preview

Preview a multipart JSON or ZIP import before committing it.

  • Auth required: Yes
  • Request body: multipart/form-data with field file.
  • Response: Same preview shape as POST /api/extensions/import/preview.

Nextcloud Integration

POST /api/nextcloud/connect

Connect the user's account to a Nextcloud instance via WebDAV.

  • Auth required: Yes
  • Request body:
    {
      "url": "string",
      "username": "string",
      "password": "string"
    }
    
  • Response:
    {
      "success": true,
      "message": "Connected to Nextcloud."
    }
    

POST /api/nextcloud/export

Export text content to the connected Nextcloud instance.

  • Auth required: Yes
  • Request body:
    {
      "text": "string",
      "title": "string"
    }
    
  • Response:
    {
      "success": true,
      "path": "string"
    }
    

POST /api/nextcloud/disconnect

Disconnect the user's Nextcloud integration.

  • Auth required: Yes
  • Request body: None
  • Response:
    {
      "success": true,
      "message": "Disconnected from Nextcloud."
    }
    

Learning Hub (Public)

Public-facing learning content endpoints. Authentication is required to track progress and submit quizzes.

GET /api/learning/categories

List all learning content categories.

  • Auth required: Yes
  • Response:
    [
      {
        "id": "number",
        "name": "string",
        "slug": "string",
        "description": "string"
      }
    ]
    

GET /api/learning/feed

Get a paginated feed of learning content.

  • Auth required: Yes
  • Query parameters:
    Parameter Type Default Description
    limit number 20 Items per page
    offset number 0 Pagination offset
  • Response:
    [
      {
        "id": "number",
        "title": "string",
        "slug": "string",
        "summary": "string",
        "category": "string",
        "created_at": "string (ISO 8601)"
      }
    ]
    

GET /api/learning/category/:slug

Get all learning content within a specific category.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    slug string Category slug
  • Response: Array of content items in the category.

GET /api/learning/content/:slug

Get a single piece of learning content, including quiz questions, answer options, and the user's progress.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    slug string Content slug
  • Response:
    {
      "id": "number",
      "title": "string",
      "slug": "string",
      "body": "string (markdown)",
      "category": "object",
      "questions": [
        {
          "id": "number",
          "text": "string",
          "options": [
            {
              "id": "number",
              "text": "string"
            }
          ]
        }
      ],
      "progress": {
        "completed": "boolean",
        "score": "number | null"
      }
    }
    

GET /api/learning/content/:slug/slides

Render Marp-formatted markdown content as HTML presentation slides.

  • Auth required: Yes
  • Path parameters:
    Parameter Type Description
    slug string Content slug
  • Response: HTML presentation content.

POST /api/learning/submit-quiz

Submit quiz answers for a piece of learning content.

  • Auth required: Yes
  • Request body:
    {
      "contentId": "number",
      "answers": [
        {
          "questionId": "number",
          "optionIds": ["number"]
        }
      ]
    }
    
  • Response:
    {
      "success": true,
      "score": "number",
      "total": "number",
      "results": [
        {
          "questionId": "number",
          "correct": "boolean"
        }
      ]
    }
    

Keyword-based search across learning content.

  • Auth required: Yes
  • Query parameters:
    Parameter Type Description
    q string Search query
  • Response: Array of matching content items.

GET /api/learning/search/semantic

Semantic (vector-based) search across learning content using embeddings.

  • Auth required: Yes
  • Query parameters:
    Parameter Type Description
    q string Search query
  • Response: Array of matching content items ranked by semantic similarity.

GET /api/learning/search/hybrid

Combined keyword and semantic search for best-of-both-worlds results.

  • Auth required: Yes
  • Query parameters:
    Parameter Type Description
    q string Search query
  • Response: Array of matching content items with combined ranking.

Learning Hub CMS (Moderator+)

Content management endpoints for learning content. Requires moderator or admin role.

GET /api/admin/learning/categories

List all learning categories (admin view).

  • Auth required: Yes (moderator+)
  • Response: Array of category objects.

POST /api/admin/learning/categories

Create a new learning category.

  • Auth required: Yes (moderator+)
  • Request body:
    {
      "name": "string",
      "slug": "string",
      "description": "string"
    }
    
  • Response:
    {
      "success": true,
      "id": "number"
    }
    

PUT /api/admin/learning/categories/:id

Update a learning category.

  • Auth required: Yes (moderator+)
  • Path parameters:
    Parameter Type Description
    id number Category ID
  • Request body:
    {
      "name": "string",
      "slug": "string",
      "description": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

DELETE /api/admin/learning/categories/:id

Delete a learning category.

  • Auth required: Yes (moderator+)
  • Path parameters:
    Parameter Type Description
    id number Category ID
  • Response:
    {
      "success": true
    }
    

GET /api/admin/learning/content

List all learning content (admin view, includes unpublished).

  • Auth required: Yes (moderator+)
  • Response: Array of content objects.

POST /api/admin/learning/content

Create new learning content.

  • Auth required: Yes (moderator+)
  • Request body:
    {
      "title": "string",
      "slug": "string",
      "body": "string (markdown)",
      "categoryId": "number",
      "questions": "array (optional)"
    }
    
  • Response:
    {
      "success": true,
      "id": "number"
    }
    

GET /api/admin/learning/content/:id

Get a single content item for editing.

  • Auth required: Yes (moderator+)
  • Path parameters:
    Parameter Type Description
    id number Content ID
  • Response: Full content object with questions and metadata.

PUT /api/admin/learning/content/:id

Update existing learning content.

  • Auth required: Yes (moderator+)
  • Path parameters:
    Parameter Type Description
    id number Content ID
  • Request body:
    {
      "title": "string",
      "slug": "string",
      "body": "string (markdown)",
      "categoryId": "number",
      "questions": "array (optional)"
    }
    
  • Response:
    {
      "success": true
    }
    

DELETE /api/admin/learning/content/:id

Delete learning content.

  • Auth required: Yes (moderator+)
  • Path parameters:
    Parameter Type Description
    id number Content ID
  • Response:
    {
      "success": true
    }
    

POST /api/admin/learning/ai-generate

Generate learning content or presentations using AI. Accepts either multipart form data (with file uploads) or JSON.

  • Auth required: Yes (moderator+)
  • Content-Type: multipart/form-data or application/json
  • Request body (JSON):
    {
      "topic": "string",
      "type": "article | presentation",
      "model": "string"
    }
    
  • Request body (multipart): Same fields plus uploaded reference files.
  • Response:
    {
      "success": true,
      "content": "string (markdown)"
    }
    

POST /api/admin/learning/ai-refine

Refine learning content body text using AI.

  • Auth required: Yes (moderator+)
  • Request body:
    {
      "body": "string",
      "instructions": "string",
      "model": "string"
    }
    
  • Response:
    {
      "success": true,
      "content": "string"
    }
    

POST /api/admin/learning/preview-slides

Preview Marp-formatted markdown as rendered presentation slides.

  • Auth required: Yes (moderator+)
  • Request body:
    {
      "markdown": "string"
    }
    
  • Response:
    {
      "css": "string",
      "slides": ["string (HTML)"]
    }
    

POST /api/admin/learning/generate-pptx

Generate a PowerPoint file from Marp markdown.

  • Auth required: Yes (moderator+)
  • Request body:
    {
      "markdown": "string",
      "title": "string"
    }
    
  • Response: Binary .pptx file download.

GET /api/admin/learning/webdav-browse

Browse files on the connected WebDAV/Nextcloud server.

  • Auth required: Yes (moderator+)
  • Query parameters:
    Parameter Type Description
    path string Directory path to browse
  • Response: Array of file/directory entries.

GET /api/admin/learning/stats

Get learning hub statistics (content counts, quiz completion rates, etc.).

  • Auth required: Yes (moderator+)
  • Response:
    {
      "totalContent": "number",
      "totalCategories": "number",
      "totalQuizSubmissions": "number",
      "averageScore": "number"
    }
    

Admin - Users

Requires admin role.

GET /api/admin/users

List all registered users.

  • Auth required: Yes (admin)
  • Response:
    {
      "success": true,
      "users": [
        {
          "id": "number",
          "name": "string",
          "email": "string",
          "role": "string",
          "email_verified": "boolean",
          "totp_enabled": "boolean",
          "disabled": "boolean",
          "created_at": "string (ISO 8601)"
        }
      ]
    }
    

GET /api/admin/users/:id

Get detailed information and usage statistics for a specific user.

  • Auth required: Yes (admin)
  • Path parameters:
    Parameter Type Description
    id number User ID
  • Response:
    {
      "success": true,
      "user": {
        "id": "number",
        "name": "string",
        "email": "string",
        "role": "string",
        "email_verified": "boolean",
        "totp_enabled": "boolean",
        "disabled": "boolean",
        "api_calls": "number",
        "last_login": "string (ISO 8601) | null"
      }
    }
    

POST /api/admin/users/:id/verify

Manually verify a user's email address (bypass email verification).

  • Auth required: Yes (admin)
  • Path parameters:
    Parameter Type Description
    id number User ID
  • Response:
    {
      "success": true
    }
    

POST /api/admin/users/:id/disable

Disable a user account, preventing login.

  • Auth required: Yes (admin)
  • Path parameters:
    Parameter Type Description
    id number User ID
  • Response:
    {
      "success": true
    }
    

POST /api/admin/users/:id/enable

Re-enable a previously disabled user account.

  • Auth required: Yes (admin)
  • Path parameters:
    Parameter Type Description
    id number User ID
  • Response:
    {
      "success": true
    }
    

Admin - Configuration

Requires admin role unless noted otherwise.

GET /api/admin/config/announcement

Get the current announcement banner. Any authenticated user can read this endpoint.

  • Auth required: Yes
  • Response:
    {
      "success": true,
      "enabled": "boolean",
      "text": "string",
      "type": "info | warning | error | success"
    }
    

GET /api/admin/config

Get all application configuration settings.

  • Auth required: Yes (admin)
  • Response: Object containing all configuration key-value pairs.

PUT /api/admin/config/:key

Update one application configuration setting. The key must match an allowed prefix: announcement., feature., email., prompt., registration_enabled, registration_invite_only, site., smtp., models., tts., stt., embeddings., clinical_assistant., or my_resources.. Anything else is rejected with 400.

Some keys are refused here even when allowed: models.* must go through the validated model endpoints, feature.* values must be true or false, and any key under lockdown returns 403.

  • Auth required: Yes (admin)
  • Request body:
    {
      "value": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

POST /api/admin/config/test-email

Send a test email to verify SMTP configuration.

  • Auth required: Yes (admin)
  • Request body:
    {
      "to": "string (email address)"
    }
    
  • Response:
    {
      "success": true,
      "message": "Test email sent."
    }
    

GET /api/admin/config/prompts

Get all AI prompt templates, including any admin overrides.

  • Auth required: Yes (admin)
  • Response: Object mapping prompt keys to their current values.

POST /api/admin/config/prompts/:key/reset

Reset a specific AI prompt template back to its default value.

  • Auth required: Yes (admin)
  • Path parameters:
    Parameter Type Description
    key string Prompt template key
  • Response:
    {
      "success": true
    }
    

POST /api/admin/config/reset-defaults

Reset all configuration settings to their default values.

  • Auth required: Yes (admin)
  • Response:
    {
      "success": true
    }
    

GET /api/admin/config/smtp/status

Check the current SMTP configuration status.

  • Auth required: Yes (admin)
  • Response:
    {
      "configured": "boolean",
      "host": "string",
      "port": "string",
      "user": "string",
      "from": "string",
      "source": "env | database | none"
    }
    

PUT /api/admin/config/smtp

Update SMTP email configuration.

  • Auth required: Yes (admin)
  • Request body:
    {
      "host": "string",
      "port": "number",
      "user": "string",
      "pass": "string",
      "from": "string",
      "secure": "boolean"
    }
    
  • Response:
    {
      "success": true
    }
    

POST /api/admin/settings/registration

Update registration settings (enable/disable new registrations).

  • Auth required: Yes (admin)
  • Request body:
    {
      "enabled": "boolean"
    }
    
  • Response:
    {
      "success": true
    }
    

GET /api/auth/oidc/config

Get the current OIDC/SSO configuration.

  • Auth required: Yes (admin)
  • Response:
    {
      "success": true,
      "config": {
        "oidc.enabled": "string",
        "oidc.issuer": "string",
        "oidc.client_id": "string",
        "oidc.client_secret": "masked string",
        "oidc.disable_local_auth": "string",
        "oidc.button_label": "string"
      }
    }
    

PUT /api/auth/oidc/config

Update the OIDC/SSO configuration.

  • Auth required: Yes (admin)
  • Request body:
    {
      "oidc.enabled": "string",
      "oidc.issuer": "string",
      "oidc.client_id": "string",
      "oidc.client_secret": "string",
      "oidc.disable_local_auth": "string",
      "oidc.button_label": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

GET /api/admin/config/models

List all AI models with their enabled/disabled status.

  • Auth required: Yes (admin)
  • Response:
    [
      {
        "id": "string",
        "name": "string",
        "enabled": "boolean",
        "isDefault": "boolean",
        "category": "string"
      }
    ]
    

PUT /api/admin/config/models/toggle

Enable or disable a specific AI model.

  • Auth required: Yes (admin)
  • Request body:
    {
      "modelId": "string",
      "enabled": "boolean"
    }
    
  • Response:
    {
      "success": true
    }
    

PUT /api/admin/config/models/default

Set the default AI model for all users.

  • Auth required: Yes (admin)
  • Request body:
    {
      "modelId": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

POST /api/admin/config/models/custom

Add a custom AI model definition.

  • Auth required: Yes (admin)
  • Request body:
    {
      "id": "string",
      "name": "string",
      "category": "string"
    }
    
  • Response:
    {
      "success": true
    }
    

GET /api/admin/config/models/discover

Discover available models from the configured AI provider.

  • Auth required: Yes (admin)
  • Response:
    {
      "success": true,
      "models": [
        {
          "id": "string",
          "name": "string"
        }
      ]
    }
    

Logs

Requires admin role.

GET /api/logs/usage

Get a 30-day usage summary broken down by model and endpoint.

  • Auth required: Yes (admin)
  • Response:
    {
      "byModel": [
        {
          "model": "string",
          "count": "number",
          "tokens": "number"
        }
      ],
      "byEndpoint": [
        {
          "endpoint": "string",
          "count": "number"
        }
      ]
    }
    

GET /api/logs/audit

Get the audit log of user actions (login, settings changes, etc.).

  • Auth required: Yes (admin)
  • Response: Array of audit log entries with timestamps, user IDs, and action descriptions.

GET /api/logs/api

Get API call metrics (request counts, latency, error rates).

  • Auth required: Yes (admin)
  • Response: Array of API metric entries.

GET /api/logs/access

Get user access history (login times, IP addresses).

  • Auth required: Yes (admin)
  • Response: Array of access log entries.

Milestones (Admin)

Developmental milestones management. Requires admin role.

GET /api/admin/milestones

List all developmental milestones in the database.

  • Auth required: Yes (admin)
  • Response: Array of milestone objects organized by age group and domain.

POST /api/admin/milestones/bulk-import

Import a set of developmental milestones in one call.

  • Auth required: Yes (admin)
  • Request body: An array of milestone objects.
  • Response:
    {
      "success": true,
      "count": "number"
    }
    

Endpoint index (not detailed above)

These exist and are in active use; the sections above carry full request and response shapes only for the surfaces the frontend touches most. Each group names its router, which stays the source of truth.

Clinical Assistant

Grounded clinical Q&A over MCP retrieval, plus the image, translation and patient-takehome tools that hang off an answer. Chats are per-user and persisted. src/routes/clinicalAssistant.js; see clinical-assistant.md.

Method Path
POST /api/clinical-assistant/chat
GET /api/clinical-assistant/chats
POST /api/clinical-assistant/chats
DELETE /api/clinical-assistant/chats/:id
GET /api/clinical-assistant/chats/:id
PATCH /api/clinical-assistant/chats/:id
POST /api/clinical-assistant/chat/stream
GET /api/clinical-assistant/examples
POST /api/clinical-assistant/image
GET /api/clinical-assistant/image/jobs
POST /api/clinical-assistant/image/jobs
GET /api/clinical-assistant/image/jobs/:id
GET /api/clinical-assistant/image/jobs/:id/download
POST /api/clinical-assistant/patient-takehome
POST /api/clinical-assistant/patient-takehome/email
GET /api/clinical-assistant/status
POST /api/clinical-assistant/translate
GET /api/clinical-assistant/translate/languages

My Resources

Personal teaching material: generate a deck or document, refine it, export it as pptx/docx/pdf/md. src/routes/myResources.js; see my-resources.md.

Method Path
GET /api/my-resources
POST /api/my-resources/generate
DELETE /api/my-resources/:id
GET /api/my-resources/:id
PUT /api/my-resources/:id
GET /api/my-resources/:id/export
POST /api/my-resources/:id/refine
GET /api/my-resources/image/jobs/:id
GET /api/my-resources/options

Notes

Personal notes with a trash/restore lifecycle, including dictated notes. src/routes/notes.js.

Method Path
GET /api/notes
POST /api/notes
POST /api/notes/from-voice
DELETE /api/notes/:id
GET /api/notes/:id
PUT /api/notes/:id
POST /api/notes/:id/restore
GET /api/notes/trash
POST /api/notes/trash/empty

Diagrams

Saved diagrams. src/routes/diagrams.js.

Method Path
GET /api/diagrams
POST /api/diagrams
DELETE /api/diagrams/:id
GET /api/diagrams/:id
PUT /api/diagrams/:id

ED Encounters

Staged ED notes and the consolidate-to-MDM finalize step. src/routes/edEncounters.js; see logic/ed-encounters.md.

Method Path
POST /api/ed-encounters/finalize
POST /api/ed-encounters/generate

Sessions

The signed-in user's own active sessions. src/routes/sessions.js.

Method Path
DELETE /api/sessions
GET /api/sessions
DELETE /api/sessions/:id

Registration Invites (Admin)

Invite codes for invite-only registration. DELETE /invites/spent clears codes that are used or expired-and-not-revoked; a live code is revoked first. src/routes/adminConfig.js.

Method Path
GET /api/admin/invites
POST /api/admin/invites
DELETE /api/admin/invites/:id
POST /api/admin/invites/:id/revoke
DELETE /api/admin/invites/spent

Admin - Documentation Viewer

Serves this docs/ tree inside the Admin panel. src/routes/adminDocs.js.

Method Path
GET /api/admin/docs/file
GET /api/admin/docs/tree

Web-search provider configuration and connectivity test.

Method Path
GET /api/admin/websearch
PUT /api/admin/websearch
POST /api/admin/websearch/test

Admin - Image Settings

Per-workflow image generation settings.

Method Path
GET /api/admin/image-settings
PUT /api/admin/image-settings/:workflow

Authentication (additional)

/api/auth/login-code/request and /api/auth/login-code/verify are the passwordless sign-in path: a 6-digit code is emailed, hashed at rest, valid for 10 minutes, single use, 5 attempts. Password sign-in always remains available, so a code that never arrives is never a lockout. The rest manage 2FA backup codes and password changes. src/routes/auth.js, src/utils/loginCodes.js.

Method Path
POST /api/auth/2fa/backup-codes
GET /api/auth/2fa/backup-codes/count
POST /api/auth/change-password
POST /api/auth/check-password
POST /api/auth/login-code/request
POST /api/auth/login-code/verify

Remaining endpoints not listed above are additional admin configuration, model/STT/TTS/embedding discovery and test calls, Learning Hub CMS operations, and the per-feature AI helpers (/api/dont-miss, /api/suggest-codes, /api/generate-pe-narrative, /api/hospital-course-update, /api/hospital-course-clarify, /api/well-visit/note, /api/milestones-data, /api/user/features, /api/logs/client-error, /api/logs/client-event, /api/generated-images/:id, /api/image-jobs/:workflow).


Health

GET /api/health

Application health check endpoint.

  • Auth required: No
  • Response:
    {
      "ok": true
    }
    

Metrics

GET /metrics

Prometheus scrape endpoint for operational metrics. Metric names use the ped_ai_ prefix and include HTTP request counters, durations, and in-flight request gauges. Unmatched 404 routes are collapsed under route="unmatched" to avoid unbounded path labels.

  • Auth required: No
  • Response: Prometheus text exposition format.