MedicAITutor Docs

MedicAI Tutor API Documentation

Interactive endpoint reference for the MedicAITutor API.

Live API Station schema OpenAPI JSON

Table of Contents

Health Check

Endpoint
GET /healthz
Description Verifies server health status. Request
No request body.
Response (200)
{
  "status": "ok",
  "env": "staging"
}

Release Info

Endpoint
GET /health/release
Description Returns machine-readable metadata for the release currently serving traffic, including the rollout timestamp, image URI, and source SHA when present. Request
curl https://api.medicaitutor.com/health/release
Response (200)
{
  "service": "medicaitutor-api",
  "environment": "staging",
  "version": "0.1.0",
  "release_at": "2026-04-20 06:16 WAT",
  "image_uri": "757052694893.dkr.ecr.us-east-1.amazonaws.com/medicaitutor:a1e8233afcc2d65ae5b826bb982d890207794119",
  "source_sha": "a1e8233afcc2d65ae5b826bb982d890207794119"
}

Stations (List)

Endpoint
GET /stations
Description Lists station summaries. By default this returns only published stations. Admin callers can add `publication_status=inactive` to inspect inactive stations or `publication_status=all` to inspect both published and inactive stations. Inactive station data is never returned to unauthenticated callers. Request
Public default:
curl https://api.medicaitutor.com/stations

Admin all-states view:
curl -H 'x-api-key: ' 'https://api.medicaitutor.com/stations?publication_status=all'
Response (200)
{
  "items": [
    {
      "slug": "abuse-in-intellectually-disabled-person-history-taking-1",
      "title": "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY FROM DAY CENTRE STAFF",
      "section": "HISTORY TAKING",
      "time_limit_minutes": 7,
      "station_category": "history_taking",
      "publication_status": "published",
      "has_rubric": true,
      "has_key_points": true,
      "has_persona": true,
      "simulation_ready": true,
      "evaluation_ready": true
    },
    {
      "slug": "abuse-in-intellectually-disabled-person-history-taking",
      "title": "ABUSE IN INTELLECTUALLY DISABLED PERSON - history",
      "section": "HISTORY TAKING",
      "time_limit_minutes": 7,
      "station_category": "history_taking",
      "publication_status": "published",
      "has_rubric": true,
      "has_key_points": true,
      "has_persona": true,
      "simulation_ready": true,
      "evaluation_ready": true
    },
    {
      "slug": "adult-adhd-discussion-4",
      "title": "ADULT ADHD - JUDO ANTI-DOPING CONSULTATION",
      "section": "MANAGEMENT",
      "time_limit_minutes": 7,
      "station_category": "management",
      "publication_status": "published",
      "has_rubric": true,
      "has_key_points": true,
      "has_persona": true,
      "simulation_ready": true,
      "evaluation_ready": true
    }
  ],
  "total": 9
}
Response (401 example)
{
  "detail": "Invalid API key."
}

Station Metadata

Endpoint
GET /stations/metadata
Description Returns available station sections and station categories. Public requests default to published stations only. Admin callers can pass `publication_status=inactive` or `publication_status=all` to inspect the broader catalog. Request
Published metadata:
curl https://api.medicaitutor.com/stations/metadata

Inactive metadata:
curl -H 'x-api-key: ' 'https://api.medicaitutor.com/stations/metadata?publication_status=inactive'
Response (200)
{
  "sections": [
    "HISTORY TAKING",
    "MANAGEMENT"
  ],
  "categories": [
    "history_taking",
    "management"
  ]
}
Response (401 example)
{
  "detail": "Invalid API key."
}

Station (By Slug)

Endpoint
GET /stations/{slug}
Description Returns the full station payload. Public requests can only read published stations. Admin callers can request `publication_status=inactive` for an inactive station or `publication_status=all` to bypass the publication filter. The publication block includes the separate boolean expert_reviewed provenance field. Path Params slug string Required Station slug (URL-safe) Request
Published station:
curl https://api.medicaitutor.com/stations/abuse-in-intellectually-disabled-person-history-taking

Inactive station:
curl -H 'x-api-key: ' 'https://api.medicaitutor.com/stations/example-slug?publication_status=inactive'
Response (200)
{
  "slug": "abuse-in-intellectually-disabled-person-history-taking",
  "section": "HISTORY TAKING",
  "title": "ABUSE IN INTELLECTUALLY DISABLED PERSON - history",
  "task": "You have been asked to speak to the key worker of a 26-year-old man with\nmoderate to severe degree of learning disability who attends the day centre. The key\nworker is concerned that Paul has been withdrawn and tearful.Obtain more history from\nthe key worker to identify causes for his presentation and arrive at a diagnosis.\nCASE SYNOPSIS\n: You're key worker for a 27-year-old man with a moderate degree of\nlearning disability who attends the day centre you work at. You contacted the mental\nhealth team because of concerns about him. A couple of days ago you noticed bruises on the\nleft side of his face and neck, they are grey/purple in colour. He hasn't been his usual self\nfor the past few days/weeks. He is easily upset, and it is often difficult to ascertain why,\nalthough he sometimes signs 'home' when distressed. He has been agitated prior to going\nhome in the evening. You have tried contacting his parents but there has been no reply to\nphone calls. He has had epilepsy most of his life, however, in recent weeks, he has been\nhaving fits more frequently, almost every day. He is currently on (tablets) Valproate and a\nnew medication. He has limited speech, mostly communicating in single words and basic\nMakaton signs to communicate basic requests\n\nYou're key worker for a 27-year-old man with a moderate degree of\nlearning disability who attends the day centre you work at. You contacted the mental\nhealth team because of concerns about him. A couple of days ago you noticed bruises on the\nleft side of his face and neck, they are grey/purple in colour. He hasn't been his usual self\nfor the past few days/weeks. He is easily upset, and it is often difficult to ascertain why,\nalthough he sometimes signs 'home' when distressed. He has been agitated prior to going\nhome in the evening. You have tried contacting his parents but there has been no reply to\nphone calls. He has had epilepsy most of his life, however, in recent weeks, he has been\nhaving fits more frequently, almost every day. He is currently on (tablets) Valproate and a\nnew medication. He has limited speech, mostly communicating in single words and basic\nMakaton signs to communicate basic requests",
  "prompts": {
    "station_name": "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
    "case_synopsis": "You have been asked to speak to the key worker of a 26-year-old man with\nmoderate to severe degree of learning disability who attends the day centre. The key\nworker is concerned that Paul has been withdrawn and tearful.Obtain more history from\nthe key worker to identify causes for his presentation and arrive at a diagnosis.\nCASE SYNOPSIS\n: You're key worker for a 27-year-old man with a moderate degree of\nlearning disability who attends the day centre you work at. You contacted the mental\nhealth team because of concerns about him. A couple of days ago you noticed bruises on the\nleft side of his face and neck, they are grey/purple in colour. He hasn't been his usual self\nfor the past few days/weeks. He is easily upset, and it is often difficult to ascertain why,\nalthough he sometimes signs 'home' when distressed. He has been agitated prior to going\nhome in the evening. You have tried contacting his parents but there has been no reply to\nphone calls. He has had epilepsy most of his life, however, in recent weeks, he has been\nhaving fits more frequently, almost every day. He is currently on (tablets) Valproate and a\nnew medication. He has limited speech, mostly communicating in single words and basic\nMakaton signs to communicate basic requests\n\nYou're key worker for a 27-year-old man with a moderate degree of\nlearning disability who attends the day centre you work at. You contacted the mental\nhealth team because of concerns about him. A couple of days ago you noticed bruises on the\nleft side of his face and neck, they are grey/purple in colour. He hasn't been his usual self\nfor the past few days/weeks. He is easily upset, and it is often difficult to ascertain why,\nalthough he sometimes signs 'home' when distressed. He has been agitated prior to going\nhome in the evening. You have tried contacting his parents but there has been no reply to\nphone calls. He has had epilepsy most of his life, however, in recent weeks, he has been\nhaving fits more frequently, almost every day. He is currently on (tablets) Valproate and a\nnew medication. He has limited speech, mostly communicating in single words and basic\nMakaton signs to communicate basic requests"
  },
  "lead": "You have been asked to speak to the key worker of a 26-year-old man with\nmoderate to severe degree of learning disability who attends the day centre. The key\nworker is concerned that Paul has been withdrawn and tearful.Obtain more history from\nthe key worker to identify causes for his presentation and arrive at a diagnosis.",
  "key_points": [
    "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
    "You have been asked to speak to the key worker of a 26-year-old man with",
    "moderate to severe degree of learning disability who attends the day centre.",
    "worker is concerned that Paul has been withdrawn and tearful.Obtain more history from",
    "the key worker to identify causes for his presentation and arrive at a diagnosis."
  ],
  "persona": {
    "name": "Standardized Patient",
    "age": 36,
    "affect": "low",
    "communication_style": "cooperative but guarded",
    "goals_concerns": [
      "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
      "You have been asked to speak to the key worker of a 26-year-old man with"
    ],
    "nonverbal_cues": [
      "low"
    ],
    "do_not_volunteer": [
      "moderate to severe degree of learning disability who attends the day centre.",
      "worker is concerned that Paul has been withdrawn and tearful.Obtain more history from"
    ]
  },
  "guardrails": {
    "forbidden_topics": [],
    "mandatory_disclosures": [
      "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
      "You have been asked to speak to the key worker of a 26-year-old man with"
    ],
    "handoff_triggers": [],
    "privacy_rules": [
      "no real third-party identifiers"
    ],
    "out_of_scope_responses": [
      "I can describe what has been happening, but I cannot decide my treatment plan myself."
    ]
  },
  "rubric": {
    "marking_sheet_title": "MARKING SHEET: ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
    "overall_scale": [
      "SEVERE FAIL",
      "FAIL",
      "BORDERLINE FAIL",
      "BORDERLINE PASS",
      "PASS",
      "EXCELLENT PASS"
    ],
    "domain_rating_scale": [
      "0",
      "1",
      "2",
      "3",
      "4"
    ],
    "rating_to_score": {
      "0": 0.0,
      "1": 0.25,
      "2": 0.5,
      "3": 0.75,
      "4": 1.0
    },
    "competency_items": [
      {
        "label": "Elicit relevant history - recent change in behaviour, withdrawal, tearfulness, and agitation before going home",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "withdrawn",
          "tearful",
          "agitated before going home",
          "past few days or weeks",
          "changed behaviour"
        ]
      },
      {
        "label": "Elicit details of unexplained bruising and possible non-accidental injury",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "bruises",
          "face and neck",
          "grey or purple",
          "injury",
          "non-accidental"
        ]
      },
      {
        "label": "Explore communication cues from Paul including distress, signing home, pain, fear, or reluctance to return home",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "Makaton",
          "signs home",
          "limited speech",
          "distress",
          "fear",
          "pain"
        ]
      },
      {
        "label": "Clarify home circumstances, parental contact, supervision, and possible neglect or abuse by carers",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "parents",
          "carers",
          "home",
          "no reply",
          "supervision",
          "neglect",
          "abuse"
        ]
      },
      {
        "label": "Assess epilepsy history, increased seizure frequency, medication changes, and medical explanations for presentation",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": false,
        "hints": [
          "epilepsy",
          "fits",
          "seizures",
          "valproate",
          "new medication",
          "medical cause"
        ]
      },
      {
        "label": "Safeguarding and risk assessment - immediate safety, documentation, escalation, and multidisciplinary action",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "safeguarding",
          "safe",
          "document bruises",
          "escalate",
          "social services",
          "police",
          "multidisciplinary"
        ]
      },
      {
        "label": "Synthesise likely abuse or safeguarding concern while considering differential causes such as seizures or medication effects",
        "weight": 1.0,
        "scoring_mode": "level",
        "critical": true,
        "hints": [
          "abuse",
          "safeguarding concern",
          "differential",
          "seizures",
          "medication",
          "diagnosis",
          "explain"
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 1,
        "title": "Missed behavioural change chronology",
        "details": "Explore withdrawal, tearfulness, agitation before going home, and timing over days or weeks."
      },
      {
        "id": 2,
        "title": "Insufficient injury and safeguarding exploration",
        "details": "Ask directly about bruising, possible non-accidental injury, home circumstances, and carer contact."
      },
      {
        "id": 3,
        "title": "Poor immediate safety planning",
        "details": "Document injuries, escalate safeguarding, consider medical causes, and ensure Paul is not returned to an unsafe setting."
      }
    ]
  },
  "evaluation": {
    "version": "ev-1.0.0"
  },
  "sources": {
    "pdf": "SPMMCourseBooklet2022.pdf"
  },
  "station_meta": {
    "topic": "ABUSE IN INTELLECTUALLY DISABLED PERSON-HISTORY TAKING",
    "time_limit_minutes": 7,
    "setting": "Clinic",
    "expected_diagnosis": "Working diagnosis",
    "station_category": "history_taking"
  },
  "publication": {
    "status": "published",
    "reason": "Expert reviewed for initial candidate testing",
    "expert_reviewed": true
  },
  "health": {
    "issues": [],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": false,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (401 example)
{
  "detail": "Invalid API key."
}
Response (404 example)
{
  "detail": "Station 'example-slug' not found."
}

Station Revisions

Endpoint
GET /stations/{slug}/revisions
Description Lists immutable snapshots saved whenever a station is created, updated, deleted, or rolled back. This endpoint is admin-only because revisions can contain inactive station data. Endpoint
POST /stations/{slug}/revisions/{revision_id}/rollback
Description Restores a previous revision as the current station and writes a new revision entry. Rollback request
No request body.
Response (200)
{
  "items": [
    {
      "id": 12,
      "slug": "abuse-in-intellectually-disabled-person-history-taking",
      "revision_number": 3,
      "action": "update",
      "created_at": "2026-03-23T10:15:00Z",
      "note": null
    }
  ],
  "total": 1
}
Response (401 example)
{
  "detail": "Admin credentials required."
}

Ask Me Anything

Endpoint
POST /ask
Description General-purpose assistant endpoint. Accepts text and optional audio/document uploads, stores the raw files, extracts text, and can answer back with AI-generated guidance. Content-Type multipart/form-data Form Fields text string Optional General question or prompt audio file Optional Audio upload to transcribe documents file[] Optional One or more documents to extract text from station_slug string Optional Station context if the user wants station-specific help exam_slug string Optional Mock exam context category string Optional Free-form hint for routing Request example
{
  "text": "Summarize the main differences between psychosis and anxiety in this station.",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "category": "study-help"
}
Response (200)
{
  "interaction": {
    "kind": "ask",
    "station_slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse",
    "category": "history_taking",
    "attachment_paths": [
      "raw/document/example.txt"
    ]
  },
  "answer": "The main difference is ...",
  "provider": "openai",
  "model": "gpt-4o-mini"
}
Response (422 example)
{
  "detail": "At least one text or attachment input is required."
}
Response (503 example)
{
  "detail": {
    "message": "nlp provider 'openai' failed.",
    "stage": "nlp",
    "provider": "openai",
    "error_type": "RuntimeError",
    "error": "Upstream provider unavailable"
  }
}

Station Review API

Technical route for submitting review text and receiving a stored AI review response. For practitioner workflow and interpretation, use the Station Review Guide.

Endpoint
POST /review
Description Accepts review text plus optional station or session context. The backend stores the submission and returns a review in the shared assistant response shape. Request payload
{
  "text": "Please review this station draft for publication readiness. Focus on rubric completeness, persona specificity, key points, and simulation safety.\n\n{\n  \"title\": \"Early menopausal symptoms assessment\",\n  \"persona\": {\"name\": \"Mrs Whitlock\", \"communication_style\": \"anxious but cooperative\"},\n  \"key_points\": [\"timeline\", \"functional impact\", \"risk\", \"ICE\"],\n  \"rubric_domains\": [\"history structure\", \"symptom exploration\", \"risk assessment\"]\n}",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "category": "station-review",
  "generate_ai_response": true
}
Response (200)
{
  "interaction": {
    "id": 8421,
    "kind": "review",
    "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
    "session_id": null,
    "exam_slug": null,
    "category": "station-review",
    "user_text": "Please review this station draft for publication readiness. Focus on rubric completeness, persona specificity, key points, and simulation safety.\n\n{\n  \"title\": \"Early menopausal symptoms assessment\",\n  \"persona\": {\"name\": \"Mrs Whitlock\", \"communication_style\": \"anxious but cooperative\"},\n  \"key_points\": [\"timeline\", \"functional impact\", \"risk\", \"ICE\"],\n  \"rubric_domains\": [\"history structure\", \"symptom exploration\", \"risk assessment\"]\n}",
    "ai_text": "Summary: the station is close to publishable, but the rubric language should be more measurable and the persona should include clearer disclosure boundaries. Improve the competency items, add one explicit safety trigger, and tighten the lead so it matches the case synopsis.",
    "payload": {
      "kind": "review",
      "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
      "session_id": null,
      "exam_slug": null,
      "category": "station-review",
      "station_title": "ABUSE IN INTELLECTUALLY DISABLED PERSON - history",
      "exam_title": null,
      "provider": "openai",
      "model": "gpt-5-mini",
      "generate_ai_response": true,
      "candidate_id": null,
      "summary": "Close to publishable; tighten rubric specificity and safety guardrails.",
      "tags": [
        "station-review",
        "publishability",
        "rubric"
      ],
      "notes": [
        "Add one more measurable competency item.",
        "Replace generic persona wording with patient-specific behaviour cues.",
        "Include a clear escalation or handoff trigger for unsafe scenarios."
      ],
      "transcript_excerpt": null,
      "evaluation_snapshot": null,
      "attachments": []
    },
    "attachment_paths": [],
    "created_at": "2026-07-17T10:30:00Z"
  },
  "extracted_text": "Please review this station draft for publication readiness. Focus on rubric completeness, persona specificity, key points, and simulation safety.\n\n{\n  \"title\": \"Early menopausal symptoms assessment\",\n  \"persona\": {\"name\": \"Mrs Whitlock\", \"communication_style\": \"anxious but cooperative\"},\n  \"key_points\": [\"timeline\", \"functional impact\", \"risk\", \"ICE\"],\n  \"rubric_domains\": [\"history structure\", \"symptom exploration\", \"risk assessment\"]\n}",
  "attachments": [],
  "provider": "openai",
  "model": "gpt-5-mini",
  "answer": "Summary: the station is close to publishable, but the rubric language should be more measurable and the persona should include clearer disclosure boundaries. Improve the competency items, add one explicit safety trigger, and tighten the lead so it matches the case synopsis.",
  "notes": [
    "Close to publishable; tighten rubric specificity and safety guardrails.",
    "Add one more measurable competency item.",
    "Replace generic persona wording with patient-specific behaviour cues."
  ]
}
Response (422 example)
{
  "detail": "Review text is required."
}

User Feedback

Endpoint
POST /feedback
Description Stores user-submitted product, station, or mock-exam feedback in the database without generating AI coaching. Payload
{
  "text": "Please make the morning paper a bit harder.",
  "exam_slug": "morning-paper",
  "category": "exam-feedback",
  "source": "web-app"
}
Response (200)
{
  "interaction": {
    "kind": "feedback",
    "exam_slug": "morning-paper",
    "category": "exam-feedback"
  },
  "accepted": true,
  "notes": [
    "Feedback stored successfully."
  ]
}
Response (422 example)
{
  "detail": "text field is required."
}

Mock Exams

Endpoint
GET /mock-exams
Request
No request body.
Response (200)
{
  "items": [
    {
      "slug": "morning-paper",
      "title": "Morning Paper",
      "session_label": "morning",
      "station_slugs": [
        "abuse-in-intellectually-disabled-person-history-taking-1",
        "abuse-in-intellectually-disabled-person-history-taking",
        "adult-adhd-discussion-4"
      ]
    }
  ],
  "total": 1
}
Endpoint
GET /mock-exams/{slug}
Request
No request body.
Response (200)
{
  "slug": "morning-paper",
  "title": "Morning Paper",
  "session_label": "morning",
  "station_slugs": [
    "abuse-in-intellectually-disabled-person-history-taking-1",
    "abuse-in-intellectually-disabled-person-history-taking",
    "adult-adhd-discussion-4"
  ]
}
Endpoint
GET /mock-exams/{slug}/station-ids
Description Returns the station slugs that belong to one mock exam. Request
No request body.
Response (200)
{
  "slug": "morning-paper",
  "station_ids": [
    "abuse-in-intellectually-disabled-person-history-taking-1",
    "abuse-in-intellectually-disabled-person-history-taking",
    "adult-adhd-discussion-4"
  ],
  "count": 3
}
Response (422 example)
{
  "detail": "Mock exam not found."
}

Chat (Text)

Endpoint
POST /chat/text
Description Candidate sends a text turn and receives the patient reply, transcript updates, and optional TTS. Request payload
{
  "station": "abuse-in-intellectually-disabled-person-history-taking",
  "messages": [
    {
      "role": "user",
      "content": "Hello, how are you feeling today?"
    }
  ],
  "include_tts": true,
  "eval_mode": "off"
}
Parameter options Response (200)
{
  "reply": "I'm okay, just feeling a bit low.",
  "messages": [
    {
      "role": "user",
      "content": "Hello, how are you feeling today?"
    },
    {
      "role": "assistant",
      "content": "I'm okay, just feeling a bit low."
    }
  ],
  "tts_url": "/media/tts/example.wav",
  "transcript": [
    {
      "role": "candidate",
      "text": "Hello, how are you feeling today?"
    },
    {
      "role": "patient",
      "text": "I'm okay, just feeling a bit low."
    }
  ],
  "session_id": "session_123",
  "metrics": {
    "t_nlp_ms": 812.44,
    "t_tts_ms": 194.12
  },
  "metadata": {
    "provider": "openai",
    "model": "gpt-4o",
    "station_slug": "abuse-in-intellectually-disabled-person-history-taking"
  }
}
Response (422 example)
{
  "detail": "Provide station or station_slug."
}
Response (503 example)
{
  "detail": {
    "message": "tts provider 'openai' failed.",
    "stage": "tts",
    "provider": "openai",
    "error_type": "RuntimeError",
    "error": "Upstream provider unavailable"
  }
}

Chat (Audio Upload)

Endpoint
POST /chat/audio?station=
Description Candidate uploads audio and receives the transcribed candidate turn, patient response, and optional TTS. Content-Type multipart/form-data Form Fields file audio/wav Required 16-bit PCM mono WAV Request example
Query: ?station=
Form data:
  file=@candidate-audio.wav
Parameter options Failure mode Returns 503 when the configured ASR provider and its fallback both reject the uploaded audio bytes. Response (200)
{
  "reply": "I'm sorry to hear that. Can you tell me more about when it started?",
  "transcript": [
    {
      "role": "candidate",
      "text": "I have been feeling on edge recently."
    },
    {
      "role": "patient",
      "text": "I'm sorry to hear that. Can you tell me more about when it started?"
    }
  ],
  "tts_url": "/media/tts/example.wav",
  "session_id": "session_123",
  "metrics": {
    "t_asr_ms": 622.15,
    "t_nlp_ms": 904.33,
    "t_tts_ms": 202.1
  },
  "metadata": {
    "asr_provider": "deepgram",
    "nlp_provider": "openai",
    "tts_provider": "openai",
    "candidate_transcript": "I have been feeling on edge recently."
  }
}
Response (422 example)
{
  "detail": "Provide station or station_slug."
}
Response (503 example)
{
  "detail": {
    "message": "asr provider 'deepgram' failed.",
    "stage": "asr",
    "provider": "deepgram",
    "error_type": "RuntimeError",
    "error": "Invalid audio payload",
    "fallback": {
      "message": "asr provider 'openai' failed.",
      "stage": "asr",
      "provider": "openai",
      "error_type": "RuntimeError",
      "error": "Invalid audio payload"
    }
  }
}

Realtime Websocket

Endpoint
GET /realtime?station=
Description Public websocket for realtime text/audio turn-taking. No API key is required. Uses the current live ASR, NLP, TTS, and avatar defaults. Query options Client hello/start payload
{
  "type": "hello",
  "session_id": null,
  "candidate_id": "candidate-1",
  "include_avatar": true,
  "avatar_provider": "liveavatar",
  "avatar_id": "default",
  "voice_id": "default",
  "metadata": {
    "mode": "exam"
  }
}
Client event types Audio config example
{
  "type": "audio_config",
  "format": "pcm_s16le"
}
Audio chunk example
{
  "type": "audio_chunk",
  "data_b64": ""
}
Server event types hello_ack example
{
  "type": "hello_ack",
  "session_id": "session_123",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "audio_supported": true
}
assistant_text example
{
  "type": "assistant_text",
  "text": "I keep hearing voices at night.",
  "provider": "openai",
  "model": "gpt-5-mini"
}
Provider bootstrap details tts_audio_chunk example
{
  "type": "tts_audio_chunk",
  "data_b64": "",
  "content_type": "audio/mpeg",
  "is_last": false
}
session_state example
{
  "type": "session_state",
  "session_id": "session_123",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "turn_count": 2,
  "state": {}
}
Response (1008 / missing station example)
{
  "type": "error",
  "code": "missing_station",
  "message": "Provide ?station= or ?station_slug=."
}
Response (1013 / rate limit example)
{
  "type": "error",
  "code": "rate_limited",
  "message": "Too many websocket sessions from this client."
}
Response (422 example)
{
  "type": "error",
  "code": "empty_transcript",
  "message": "No speech was detected for this turn."
}
Response (1011 / turn failure example)
{
  "type": "error",
  "code": "turn_failed",
  "message": "Upstream provider unavailable"
}

Live Provider Defaults

Purpose Change the live ASR, NLP, and TTS defaults once, and the stack uses those selections across chat, audio, and websocket flows. Selectable providers Read current defaults
GET /admin/provider-defaults
Update live defaults
PUT /admin/provider-defaults
Update request payload
{
  "asr_provider": "deepgram",
  "nlp_provider": "openai",
  "tts_provider": "openai",
  "actor": "admin",
  "note": "Switch production defaults after smoke test.",
  "source": "admin_ui"
}
Update response (200)
{
  "defaults": {
    "asr": {
      "category": "asr",
      "provider_name": "deepgram"
    },
    "nlp": {
      "category": "nlp",
      "provider_name": "openai"
    },
    "tts": {
      "category": "tts",
      "provider_name": "openai"
    }
  },
  "supported": {
    "asr": [
      "faster_whisper",
      "whisper",
      "openai",
      "deepgram",
      "elevenlabs",
      "azure",
      "aws_transcribe_medical",
      "parakeet_stream",
      "multitalker_parakeet"
    ],
    "nlp": [
      "openai"
    ],
    "tts": [
      "elevenlabs",
      "azure",
      "deepgram",
      "openai",
      "polly"
    ]
  },
  "updated": true
}
Response (401 example)
{
  "detail": "Invalid API key."
}
Response (422 example)
{
  "detail": [
    {
      "loc": [
        "body",
        "nlp_provider"
      ],
      "msg": "Input should be 'openai'",
      "type": "literal_error"
    }
  ]
}
Audit log
GET /admin/provider-defaults/audit?limit=25
Runtime status
GET /providers/status
Advanced diagnostics
These routes still exist for direct provider inspection:
GET /asr/faster_whisper
POST /asr/faster_whisper/batch
GET /tts/openai
POST /tts/openai/speak
GET /nlp/openai
POST /nlp/openai/simulated-patient

Sessions

Endpoint
POST /session/start
Description Starts a new OSCE session for a chosen station. Request payload
{
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "candidate_id": "candidate-1",
  "session_id": "session_123"
}
Response (200)
{
  "session_id": "session_123"
}
Endpoint
POST /sessions
Description Creates a backend session record directly when you want the lower-level sessions API. Endpoint
GET /sessions
Description Lists session summaries. Response (200)
{
  "items": [
    {
      "session_id": "session_123",
      "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
      "turn_count": 4
    }
  ],
  "total": 1
}
Endpoint
GET /sessions/{session_id}
Description Returns the transcript, timestamps, and current station state. Response (200)
{
  "session_id": "session_123",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "transcript": [
    {
      "role": "candidate",
      "text": "What brought you in today?"
    },
    {
      "role": "patient",
      "text": "I have been anxious for months."
    }
  ]
}
Endpoint
POST /session/end
Description Closes the session and returns the final examiner summary. The session must already contain transcript turns, or you must provide transcript items in the request body. Request payload
{
  "session_id": "session_123",
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "transcript": [
    {
      "role": "candidate",
      "text": "What brought you in today?"
    },
    {
      "role": "patient",
      "text": "I have been anxious for months."
    }
  ]
}
Response (200)
{
  "session_id": "session_123",
  "overall_band": "PASS",
  "tips": [
    "Explore risk earlier.",
    "Summarize more explicitly before closing."
  ],
  "evaluation": {
    "overall_rating": "PASS"
  }
}
Response (422 example)
{
  "detail": "A transcript or session_id is required."
}

Realtime Websocket (Internal)

Endpoint
GET /ws/station/{station_slug}/session
Description Lower-level internal websocket. Requires API key auth. Uses the same orchestration engine as `/realtime`, but preserves the older internal event names. Auth Requires x-api-key header or api_key query parameter when API auth is enabled. Client event types Server event types final_transcript example
{
  "type": "final_transcript",
  "text": "Can you tell me what happened last night?"
}
patient_response_text example
{
  "type": "patient_response_text",
  "text": "I had to get out of the flat because the voices were getting too much.",
  "provider": "openai",
  "model": "gpt-5-mini"
}
Response (401 example)
{
  "type": "error",
  "code": "unauthorized",
  "message": "Invalid API key."
}

Evaluation

Endpoint
POST /evaluate
Description Evaluates a station transcript and returns the full examiner-scoring payload. You may supply either a transcript directly, a session_id that already has transcript turns, or both. Request payload
{
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "session_id": "session_123",
  "transcript": [
    {
      "role": "candidate",
      "text": "What brought you in today?"
    },
    {
      "role": "patient",
      "text": "I have been anxious for months."
    }
  ],
  "feedback_mode": "examiner"
}
Response (200)
{
  "overall_rating": "PASS",
  "total_score": 0.71,
  "domain_scores": [
    {
      "label": "History and symptom exploration",
      "rating": "Good",
      "score": 0.75,
      "rationale": "Explored the main anxiety symptoms and time course clearly.",
      "evidence": [
        "Clarified chronic course of anxiety",
        "Asked about impairment across settings"
      ]
    }
  ],
  "strengths": [
    "Good structure overall."
  ],
  "missed_points": [
    "Risk assessment needed more depth."
  ],
  "feedback": [
    "Good structure overall.",
    "Risk assessment needed more depth."
  ],
  "critical_failures": []
}
Response (422 example)
{
  "detail": "A transcript or session_id is required."
}
Endpoint
POST /evaluation/score
Description Scores the session transcript using the station rubric and returns the examiner view. Request payload
{
  "station_slug": "abuse-in-intellectually-disabled-person-history-taking",
  "transcript": [
    {
      "role": "candidate",
      "text": "What brought you in today?"
    },
    {
      "role": "patient",
      "text": "I have been anxious for months."
    }
  ]
}
Response (200)
{
  "overall_rating": "PASS",
  "total_score": 0.71,
  "feedback": [
    "Improve: Explore risk and safety more explicitly.",
    "Use open questions early, then narrow into the key diagnosis."
  ]
}
Response (422 example)
{
  "detail": "station_slug is required."
}

Complete Endpoint Catalog

Authoritative catalog generated from the live OpenAPI schema. Routes intentionally hidden from the public guide are excluded here. Expand any card for exact request bodies, parameters, and documented responses.

Asr

POST/asr/batch

Batch Transcribe

Request payload (multipart/form-data)
{
  "audio": ""
}
Response (200)
{
  "transcript": "",
  "segments": [
    {
      "start": 0.0,
      "end": 0.0,
      "text": "",
      "confidence": 0.0,
      "is_final": true
    }
  ],
  "provider": "",
  "model": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/asr/{provider}

Get Provider Status

Parameters
NameInRequiredTypeDescription
providerpathYesstringASR provider name. Options: faster_whisper, whisper, openai, deepgram, elevenlabs, azure, aws_transcribe_medical, parakeet_stream, multitalker_parakeet.
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/asr/{provider}/batch

Batch Transcribe Provider

Parameters
NameInRequiredTypeDescription
providerpathYesstringASR provider name. Options: faster_whisper, whisper, openai, deepgram, elevenlabs, azure, aws_transcribe_medical, parakeet_stream, multitalker_parakeet.
Request payload (multipart/form-data)
{
  "audio": ""
}
Response (200)
{
  "transcript": "",
  "segments": [
    {
      "start": 0.0,
      "end": 0.0,
      "text": "",
      "confidence": 0.0,
      "is_final": true
    }
  ],
  "provider": "",
  "model": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Avatar

GET/avatar/jobs/{job_id}

Get Avatar Job

Parameters
NameInRequiredTypeDescription
job_idpathYesstring
providerqueryNostring
Response (200)
{
  "provider": "did",
  "mode": "",
  "status": "created",
  "session_id": "",
  "vendor_session_id": "",
  "job_id": "",
  "stream_url": "",
  "video_url": "",
  "thumbnail_url": "",
  "token": "",
  "expires_at": "",
  "capabilities": {
    "provider": "did",
    "supports_live_session": true,
    "supports_async_render": true,
    "requires_client_sdk": true,
    "requires_webrtc": true,
    "supports_text_input": true,
    "supports_audio_input": true,
    "enabled": true,
    "healthy": true,
    "detail": ""
  },
  "metadata": {},
  "error": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/avatar/providers/status

Avatar Provider Status

Response (200)
{
  "providers": [
    {
      "provider": "did",
      "capabilities": {
        "provider": "did",
        "supports_live_session": true,
        "supports_async_render": true,
        "requires_client_sdk": true,
        "requires_webrtc": true,
        "supports_text_input": true,
        "supports_audio_input": true,
        "enabled": true,
        "healthy": true,
        "detail": ""
      }
    }
  ]
}
POST/avatar/render

Create Avatar Render

Request payload (application/json)
{
  "provider": "did",
  "session_id": "",
  "station_slug": "",
  "text": "",
  "audio_url": "",
  "avatar_id": "",
  "voice_id": "",
  "metadata": {}
}
Response (200)
{
  "provider": "did",
  "mode": "",
  "status": "created",
  "session_id": "",
  "vendor_session_id": "",
  "job_id": "",
  "stream_url": "",
  "video_url": "",
  "thumbnail_url": "",
  "token": "",
  "expires_at": "",
  "capabilities": {
    "provider": "did",
    "supports_live_session": true,
    "supports_async_render": true,
    "requires_client_sdk": true,
    "requires_webrtc": true,
    "supports_text_input": true,
    "supports_audio_input": true,
    "enabled": true,
    "healthy": true,
    "detail": ""
  },
  "metadata": {},
  "error": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/avatar/session

Create Avatar Session

Request payload (application/json)
{
  "provider": "did",
  "station_slug": "",
  "session_id": "",
  "avatar_id": "",
  "voice_id": "",
  "metadata": {}
}
Response (200)
{
  "provider": "did",
  "mode": "",
  "status": "created",
  "session_id": "",
  "vendor_session_id": "",
  "job_id": "",
  "stream_url": "",
  "video_url": "",
  "thumbnail_url": "",
  "token": "",
  "expires_at": "",
  "capabilities": {
    "provider": "did",
    "supports_live_session": true,
    "supports_async_render": true,
    "requires_client_sdk": true,
    "requires_webrtc": true,
    "supports_text_input": true,
    "supports_audio_input": true,
    "enabled": true,
    "healthy": true,
    "detail": ""
  },
  "metadata": {},
  "error": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
DELETE/avatar/session/{session_id}

Close Avatar Session

Parameters
NameInRequiredTypeDescription
session_idpathYesstring
providerqueryNostring
Response (200)
{
  "provider": "did",
  "mode": "",
  "status": "created",
  "session_id": "",
  "vendor_session_id": "",
  "job_id": "",
  "stream_url": "",
  "video_url": "",
  "thumbnail_url": "",
  "token": "",
  "expires_at": "",
  "capabilities": {
    "provider": "did",
    "supports_live_session": true,
    "supports_async_render": true,
    "requires_client_sdk": true,
    "requires_webrtc": true,
    "supports_text_input": true,
    "supports_audio_input": true,
    "enabled": true,
    "healthy": true,
    "detail": ""
  },
  "metadata": {},
  "error": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Evaluation

POST/evaluation/score

Score Session

Request payload (application/json)
{
  "station_slug": "",
  "session_id": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "mode": ""
}
Response (200)
{
  "overall_rating": "",
  "total_score": 0.0,
  "score_percent": 0.0,
  "domain_scores": [
    {
      "label": "",
      "rating": "",
      "score": 0.0,
      "rationale": "",
      "evidence": [
        ""
      ]
    }
  ],
  "strengths": [
    ""
  ],
  "missed_points": [
    ""
  ],
  "feedback": [
    ""
  ],
  "critical_failures": [
    {
      "label": "",
      "rationale": ""
    }
  ],
  "performance_breakdown": {
    "strongest_domains": [
      ""
    ],
    "improvement_domains": [
      ""
    ],
    "domains": [
      {
        "label": "",
        "rating": "",
        "score": 0.0,
        "score_percent": 0.0,
        "rationale": "",
        "evidence": [
          ""
        ],
        "strength": true,
        "improvement_priority": true
      }
    ]
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Nlp

POST/nlp/simulated-patient

Simulated Patient

Request payload (application/json)
{
  "station_slug": "",
  "candidate_message": "",
  "conversation_history": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "session_id": ""
}
Response (200)
{
  "patient_response": "",
  "state": {
    "distress_level": "",
    "handoff_required": true,
    "end_status": "",
    "disclosed_items": [
      ""
    ],
    "rapport_level": 0,
    "turn_index": 0,
    "topics_covered": [
      ""
    ],
    "last_candidate_intent": "",
    "last_patient_focus": "",
    "facts_revealed_by_topic": {},
    "conversation_summary": [
      ""
    ],
    "recent_candidate_themes": [
      ""
    ],
    "reply_style": "",
    "notes": {}
  },
  "provider": "",
  "model": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/nlp/{provider}

Get Provider Status

Parameters
NameInRequiredTypeDescription
providerpathYesstringNLP provider name. Options: openai.
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/nlp/{provider}/simulated-patient

Simulated Patient Provider

Parameters
NameInRequiredTypeDescription
providerpathYesstring
Request payload (application/json)
{
  "station_slug": "",
  "candidate_message": "",
  "conversation_history": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "session_id": ""
}
Response (200)
{
  "patient_response": "",
  "state": {
    "distress_level": "",
    "handoff_required": true,
    "end_status": "",
    "disclosed_items": [
      ""
    ],
    "rapport_level": 0,
    "turn_index": 0,
    "topics_covered": [
      ""
    ],
    "last_candidate_intent": "",
    "last_patient_focus": "",
    "facts_revealed_by_topic": {},
    "conversation_summary": [
      ""
    ],
    "recent_candidate_themes": [
      ""
    ],
    "reply_style": "",
    "notes": {}
  },
  "provider": "",
  "model": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Public Api

GET/

Root

Response (200)
{}
POST/ask

Public Ask

Request payload (application/json)
{
  "text": "",
  "station_slug": "",
  "session_id": "",
  "candidate_id": "",
  "category": "",
  "exam_slug": "",
  "feedback_text": "",
  "history": [
    {
      "role": "candidate",
      "text": "",
      "speaker": "candidate",
      "content": ""
    }
  ],
  "generate_ai_response": true,
  "message": ""
}
Response (200)
{
  "interaction": {
    "id": 0,
    "kind": "ask",
    "station_slug": "",
    "session_id": "",
    "exam_slug": "",
    "category": "",
    "user_text": "",
    "ai_text": "",
    "payload": {},
    "attachment_paths": [
      ""
    ],
    "created_at": ""
  },
  "extracted_text": "",
  "attachments": [
    {
      "filename": "",
      "media_path": "",
      "content_type": "",
      "extracted_text": "",
      "size_bytes": 0
    }
  ],
  "provider": "",
  "model": "",
  "answer": "",
  "notes": [
    ""
  ]
}
Response (422)
{
  "detail": "Provide text, message, feedback_text, audio, or documents."
}
GET/attempts

Public List Attempts

Parameters
NameInRequiredTypeDescription
candidate_idqueryNostring
datequeryNostring
limitqueryNointeger
Response (200)
{
  "items": [
    {
      "attempt_id": "",
      "candidate_id": "",
      "attempt_type": "",
      "exam_slug": "",
      "status": "active",
      "started_at": "",
      "ended_at": "",
      "attempt_date": "",
      "station_slugs": [
        ""
      ],
      "s3_prefix": "",
      "summary": {},
      "metadata": {}
    }
  ],
  "total": 0
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/attempts/start

Public Attempt Start

Request payload (application/json)
{
  "candidate_id": "",
  "attempt_id": "",
  "attempt_type": "single_station",
  "exam_slug": "",
  "station_slugs": [
    ""
  ],
  "metadata": {}
}
Response (200)
{
  "attempt": {
    "attempt_id": "",
    "candidate_id": "",
    "attempt_type": "",
    "exam_slug": "",
    "status": "active",
    "started_at": "",
    "ended_at": "",
    "attempt_date": "",
    "station_slugs": [
      ""
    ],
    "s3_prefix": "",
    "summary": {},
    "metadata": {}
  },
  "date_index_key": "",
  "manifest_key": "",
  "summary_key": "",
  "bucket": "",
  "media_prefix": "",
  "date_index_s3_uri": "",
  "manifest_s3_uri": "",
  "summary_s3_uri": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/attempts/{attempt_id}

Public Get Attempt

Parameters
NameInRequiredTypeDescription
attempt_idpathYesstring
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/candidates/{candidate_id}/attempts

Public List Candidate Attempts

Parameters
NameInRequiredTypeDescription
candidate_idpathYesstring
datequeryNostring
limitqueryNointeger
Response (200)
{
  "items": [
    {
      "attempt_id": "",
      "candidate_id": "",
      "attempt_type": "",
      "exam_slug": "",
      "status": "active",
      "started_at": "",
      "ended_at": "",
      "attempt_date": "",
      "station_slugs": [
        ""
      ],
      "s3_prefix": "",
      "summary": {},
      "metadata": {}
    }
  ],
  "total": 0
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/chat/audio

Public Chat Audio

Parameters
NameInRequiredTypeDescription
stationqueryNostring
session_idqueryNostring
Request payload (multipart/form-data)
{
  "station_slug": "",
  "candidate_id": "",
  "file": "",
  "include_avatar": "",
  "avatar_provider": ""
}
Response (200)
{
  "reply": "",
  "messages": [
    {
      "role": "user",
      "content": ""
    }
  ],
  "tts_url": "",
  "avatar": {
    "provider": "did",
    "mode": "",
    "status": "created",
    "session_id": "",
    "vendor_session_id": "",
    "job_id": "",
    "stream_url": "",
    "video_url": "",
    "thumbnail_url": "",
    "token": "",
    "expires_at": "",
    "capabilities": {
      "provider": "did",
      "supports_live_session": true,
      "supports_async_render": true,
      "requires_client_sdk": true,
      "requires_webrtc": true,
      "supports_text_input": true,
      "supports_audio_input": true,
      "enabled": true,
      "healthy": true,
      "detail": ""
    },
    "metadata": {},
    "error": ""
  },
  "error": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "evaluation": {
    "overall_rating": "",
    "total_score": 0.0,
    "score_percent": 0.0,
    "domain_scores": [
      {
        "label": "",
        "rating": "",
        "score": 0.0,
        "rationale": "",
        "evidence": [
          ""
        ]
      }
    ],
    "strengths": [
      ""
    ],
    "missed_points": [
      ""
    ],
    "feedback": [
      ""
    ],
    "critical_failures": [
      {
        "label": "",
        "rationale": ""
      }
    ],
    "performance_breakdown": {
      "strongest_domains": [
        ""
      ],
      "improvement_domains": [
        ""
      ],
      "domains": [
        {}
      ]
    }
  },
  "feedback": [
    ""
  ],
  "session_id": "",
  "metrics": {},
  "metadata": {}
}
Response (422)
{
  "detail": "Provide station or station_slug."
}
Response (503)
{
  "detail": {
    "message": "asr provider 'deepgram' failed.",
    "stage": "asr",
    "provider": "deepgram",
    "error_type": "RuntimeError",
    "error": "Invalid audio payload",
    "fallback": {
      "message": "asr provider 'openai' failed.",
      "stage": "asr",
      "provider": "openai",
      "error_type": "RuntimeError",
      "error": "Invalid audio payload"
    }
  }
}
POST/chat/text

Public Chat Text

Request payload (application/json)
{
  "station": "",
  "station_slug": "",
  "session_id": "",
  "candidate_id": "",
  "messages": [
    {
      "role": "user",
      "content": ""
    }
  ],
  "user_input": "",
  "include_tts": true,
  "include_avatar": true,
  "avatar_provider": "did",
  "dry_run": true,
  "eval_mode": "",
  "feedback_mode": ""
}
Response (200)
{
  "reply": "",
  "messages": [
    {
      "role": "user",
      "content": ""
    }
  ],
  "tts_url": "",
  "avatar": {
    "provider": "did",
    "mode": "",
    "status": "created",
    "session_id": "",
    "vendor_session_id": "",
    "job_id": "",
    "stream_url": "",
    "video_url": "",
    "thumbnail_url": "",
    "token": "",
    "expires_at": "",
    "capabilities": {
      "provider": "did",
      "supports_live_session": true,
      "supports_async_render": true,
      "requires_client_sdk": true,
      "requires_webrtc": true,
      "supports_text_input": true,
      "supports_audio_input": true,
      "enabled": true,
      "healthy": true,
      "detail": ""
    },
    "metadata": {},
    "error": ""
  },
  "error": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "evaluation": {
    "overall_rating": "",
    "total_score": 0.0,
    "score_percent": 0.0,
    "domain_scores": [
      {
        "label": "",
        "rating": "",
        "score": 0.0,
        "rationale": "",
        "evidence": [
          ""
        ]
      }
    ],
    "strengths": [
      ""
    ],
    "missed_points": [
      ""
    ],
    "feedback": [
      ""
    ],
    "critical_failures": [
      {
        "label": "",
        "rationale": ""
      }
    ],
    "performance_breakdown": {
      "strongest_domains": [
        ""
      ],
      "improvement_domains": [
        ""
      ],
      "domains": [
        {}
      ]
    }
  },
  "feedback": [
    ""
  ],
  "session_id": "",
  "metrics": {},
  "metadata": {}
}
Response (422)
{
  "detail": "Provide station or station_slug."
}
Response (503)
{
  "detail": {
    "message": "tts provider 'openai' failed.",
    "stage": "tts",
    "provider": "openai",
    "error_type": "RuntimeError",
    "error": "Upstream provider unavailable"
  }
}
POST/evaluate

Public Evaluate

Request payload (application/json)
{
  "station_slug": "",
  "session_id": "",
  "candidate_id": "",
  "attempt_id": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "speaker": "candidate",
      "content": ""
    }
  ],
  "feedback_mode": ""
}
Response (200)
{
  "overall_rating": "",
  "total_score": 0.0,
  "score_percent": 0.0,
  "domain_scores": [
    {
      "label": "",
      "rating": "",
      "score": 0.0,
      "rationale": "",
      "evidence": [
        ""
      ]
    }
  ],
  "strengths": [
    ""
  ],
  "missed_points": [
    ""
  ],
  "feedback": [
    ""
  ],
  "critical_failures": [
    {
      "label": "",
      "rationale": ""
    }
  ],
  "performance_breakdown": {
    "strongest_domains": [
      ""
    ],
    "improvement_domains": [
      ""
    ],
    "domains": [
      {
        "label": "",
        "rating": "",
        "score": 0.0,
        "score_percent": 0.0,
        "rationale": "",
        "evidence": [
          ""
        ],
        "strength": true,
        "improvement_priority": true
      }
    ]
  }
}
Response (422)
{
  "detail": "A transcript or session_id is required."
}
POST/feedback

Public Feedback

Request payload (application/json)
{
  "text": "",
  "station_slug": "",
  "session_id": "",
  "exam_slug": "",
  "category": "",
  "page_url": "",
  "source": ""
}
Response (200)
{
  "interaction": {
    "id": 0,
    "kind": "ask",
    "station_slug": "",
    "session_id": "",
    "exam_slug": "",
    "category": "",
    "user_text": "",
    "ai_text": "",
    "payload": {},
    "attachment_paths": [
      ""
    ],
    "created_at": ""
  },
  "accepted": true,
  "notes": [
    ""
  ]
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/media/{path}

Get Media

Parameters
NameInRequiredTypeDescription
pathpathYesstring
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/mock-exams

List Mock Exams

Parameters
NameInRequiredTypeDescription
seedqueryNostring
Response (200)
{
  "items": [
    {
      "slug": "",
      "title": "",
      "session_label": "",
      "station_slugs": [
        ""
      ],
      "notes": {},
      "created_at": "",
      "updated_at": ""
    }
  ],
  "total": 0
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/mock-exams/{slug}

Get Mock Exam

Parameters
NameInRequiredTypeDescription
slugpathYesstring
seedqueryNostring
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/mock-exams/{slug}/station-ids

Get Mock Exam Station Ids

Parameters
NameInRequiredTypeDescription
slugpathYesstring
seedqueryNostring
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/provider-defaults

Provider Defaults Admin Page

Response (200)
HTML document response.
POST/review

Public Review

Request payload (application/json)
{
  "text": "",
  "station_slug": "",
  "session_id": "",
  "exam_slug": "",
  "category": "",
  "generate_ai_response": true
}
Response (200)
{
  "interaction": {
    "id": 0,
    "kind": "ask",
    "station_slug": "",
    "session_id": "",
    "exam_slug": "",
    "category": "",
    "user_text": "",
    "ai_text": "",
    "payload": {},
    "attachment_paths": [
      ""
    ],
    "created_at": ""
  },
  "extracted_text": "",
  "attachments": [
    {
      "filename": "",
      "media_path": "",
      "content_type": "",
      "extracted_text": "",
      "size_bytes": 0
    }
  ],
  "provider": "",
  "model": "",
  "answer": "",
  "notes": [
    ""
  ]
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/session/end

Public Session End

Request payload (application/json)
{
  "session_id": "",
  "station_slug": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "speaker": "candidate",
      "content": ""
    }
  ],
  "reason": ""
}
Response (200)
{
  "session_id": "",
  "domains": {},
  "checklist": {},
  "overall_band": "",
  "tips": [
    ""
  ],
  "evaluation": {
    "overall_rating": "",
    "total_score": 0.0,
    "score_percent": 0.0,
    "domain_scores": [
      {
        "label": "",
        "rating": "",
        "score": 0.0,
        "rationale": "",
        "evidence": [
          ""
        ]
      }
    ],
    "strengths": [
      ""
    ],
    "missed_points": [
      ""
    ],
    "feedback": [
      ""
    ],
    "critical_failures": [
      {
        "label": "",
        "rationale": ""
      }
    ],
    "performance_breakdown": {
      "strongest_domains": [
        ""
      ],
      "improvement_domains": [
        ""
      ],
      "domains": [
        {}
      ]
    }
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/session/start

Public Session Start

Request payload (application/json)
{
  "station_slug": "",
  "candidate_id": "",
  "session_id": "",
  "attempt_id": "",
  "exam_slug": "",
  "attempt_type": "",
  "metadata": {}
}
Response (200)
{
  "session_id": ""
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/user-feedback

Public User Feedback

Request payload (application/json)
{
  "text": "",
  "station_slug": "",
  "session_id": "",
  "exam_slug": "",
  "category": "",
  "page_url": "",
  "source": ""
}
Response (200)
{
  "interaction": {
    "id": 0,
    "kind": "ask",
    "station_slug": "",
    "session_id": "",
    "exam_slug": "",
    "category": "",
    "user_text": "",
    "ai_text": "",
    "payload": {},
    "attachment_paths": [
      ""
    ],
    "created_at": ""
  },
  "accepted": true,
  "notes": [
    ""
  ]
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Sessions

GET/sessions

List Sessions

Response (200)
[
  {
    "session_id": "",
    "station_slug": "",
    "status": "",
    "started_at": "",
    "ended_at": "",
    "turn_count": 0,
    "message_count": 0,
    "exchange_count": 0,
    "candidate_turn_count": 0,
    "patient_turn_count": 0
  }
]
POST/sessions

Create Session

Request payload (application/json)
{
  "station_slug": "",
  "session_id": "",
  "candidate_id": "",
  "metadata": {}
}
Response (201)
{
  "session_id": "",
  "station_slug": "",
  "status": "active",
  "started_at": "",
  "ended_at": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "state": {
    "distress_level": "",
    "handoff_required": true,
    "end_status": "",
    "disclosed_items": [
      ""
    ],
    "rapport_level": 0,
    "turn_index": 0,
    "topics_covered": [
      ""
    ],
    "last_candidate_intent": "",
    "last_patient_focus": "",
    "facts_revealed_by_topic": {},
    "conversation_summary": [
      ""
    ],
    "recent_candidate_themes": [
      ""
    ],
    "reply_style": "",
    "notes": {}
  },
  "metadata": {}
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/sessions/{session_id}

Get Session

Parameters
NameInRequiredTypeDescription
session_idpathYesstring
Response (200)
{
  "session_id": "",
  "station_slug": "",
  "status": "active",
  "started_at": "",
  "ended_at": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "state": {
    "distress_level": "",
    "handoff_required": true,
    "end_status": "",
    "disclosed_items": [
      ""
    ],
    "rapport_level": 0,
    "turn_index": 0,
    "topics_covered": [
      ""
    ],
    "last_candidate_intent": "",
    "last_patient_focus": "",
    "facts_revealed_by_topic": {},
    "conversation_summary": [
      ""
    ],
    "recent_candidate_themes": [
      ""
    ],
    "reply_style": "",
    "notes": {}
  },
  "metadata": {}
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/sessions/{session_id}/end

End Session

Parameters
NameInRequiredTypeDescription
session_idpathYesstring
Request payload (application/json)
{
  "reason": ""
}
Response (200)
{
  "session_id": "",
  "station_slug": "",
  "status": "active",
  "started_at": "",
  "ended_at": "",
  "transcript": [
    {
      "role": "candidate",
      "text": "",
      "timestamp": "",
      "metadata": {}
    }
  ],
  "state": {
    "distress_level": "",
    "handoff_required": true,
    "end_status": "",
    "disclosed_items": [
      ""
    ],
    "rapport_level": 0,
    "turn_index": 0,
    "topics_covered": [
      ""
    ],
    "last_candidate_intent": "",
    "last_patient_focus": "",
    "facts_revealed_by_topic": {},
    "conversation_summary": [
      ""
    ],
    "recent_candidate_themes": [
      ""
    ],
    "reply_style": "",
    "notes": {}
  },
  "metadata": {}
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

Stations

GET/stations

List Stations

Parameters
NameInRequiredTypeDescription
publication_statusqueryNostringFilter returned stations by publication state. Public requests may only use 'published'.
Response (200)
{
  "items": [
    {
      "slug": "",
      "section": "",
      "title": "",
      "time_limit_minutes": 0,
      "station_category": "",
      "publication_status": "published",
      "has_rubric": true,
      "has_key_points": true,
      "has_persona": true,
      "simulation_ready": true,
      "evaluation_ready": true
    }
  ],
  "total": 0
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/stations

Create Station

Request payload (application/json)
{
  "slug": "",
  "section": "",
  "title": "",
  "task": "",
  "prompts": {
    "station_name": "",
    "case_synopsis": ""
  },
  "lead": "",
  "key_points": [
    ""
  ],
  "persona": {
    "name": "",
    "age": 0,
    "affect": "",
    "communication_style": "",
    "goals_concerns": [
      ""
    ],
    "nonverbal_cues": [
      ""
    ],
    "do_not_volunteer": [
      ""
    ]
  },
  "guardrails": {
    "forbidden_topics": [
      ""
    ],
    "mandatory_disclosures": [
      ""
    ],
    "handoff_triggers": [
      ""
    ],
    "privacy_rules": [
      ""
    ],
    "out_of_scope_responses": [
      ""
    ]
  },
  "rubric": {
    "marking_sheet_title": "",
    "overall_scale": [
      ""
    ],
    "domain_rating_scale": [
      ""
    ],
    "rating_to_score": {},
    "competency_items": [
      {
        "label": "",
        "weight": 0.0,
        "scoring_mode": "",
        "critical": true,
        "hints": [
          ""
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 0,
        "title": "",
        "details": ""
      }
    ]
  },
  "evaluation": {
    "version": ""
  },
  "sources": {
    "pdf": ""
  },
  "station_meta": {
    "topic": "",
    "time_limit_minutes": 0,
    "setting": "",
    "expected_diagnosis": "",
    "station_category": ""
  },
  "publication": {
    "status": "published",
    "reason": "",
    "expert_reviewed": true
  },
  "health": {
    "issues": [
      ""
    ],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": true,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (201)
{
  "slug": "",
  "section": "",
  "title": "",
  "task": "",
  "prompts": {
    "station_name": "",
    "case_synopsis": ""
  },
  "lead": "",
  "key_points": [
    ""
  ],
  "persona": {
    "name": "",
    "age": 0,
    "affect": "",
    "communication_style": "",
    "goals_concerns": [
      ""
    ],
    "nonverbal_cues": [
      ""
    ],
    "do_not_volunteer": [
      ""
    ]
  },
  "guardrails": {
    "forbidden_topics": [
      ""
    ],
    "mandatory_disclosures": [
      ""
    ],
    "handoff_triggers": [
      ""
    ],
    "privacy_rules": [
      ""
    ],
    "out_of_scope_responses": [
      ""
    ]
  },
  "rubric": {
    "marking_sheet_title": "",
    "overall_scale": [
      ""
    ],
    "domain_rating_scale": [
      ""
    ],
    "rating_to_score": {},
    "competency_items": [
      {
        "label": "",
        "weight": 0.0,
        "scoring_mode": "",
        "critical": true,
        "hints": [
          ""
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 0,
        "title": "",
        "details": ""
      }
    ]
  },
  "evaluation": {
    "version": ""
  },
  "sources": {
    "pdf": ""
  },
  "station_meta": {
    "topic": "",
    "time_limit_minutes": 0,
    "setting": "",
    "expected_diagnosis": "",
    "station_category": ""
  },
  "publication": {
    "status": "published",
    "reason": "",
    "expert_reviewed": true
  },
  "health": {
    "issues": [
      ""
    ],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": true,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/stations/categories/replace

Replace Station Category

Request payload (application/json)
{
  "from_value": "",
  "to_value": ""
}
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/stations/metadata

Get Station Metadata

Parameters
NameInRequiredTypeDescription
publication_statusqueryNostringFilter metadata by publication state. Public requests may only use 'published'.
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/stations/review

Review Stations Page

Response (200)
HTML document response.
POST/stations/sections/replace

Replace Station Section

Request payload (application/json)
{
  "from_value": "",
  "to_value": ""
}
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
DELETE/stations/{slug}

Delete Station

Parameters
NameInRequiredTypeDescription
slugpathYesstring
Response (204)
No structured response body documented.
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/stations/{slug}

Get Station

Parameters
NameInRequiredTypeDescription
slugpathYesstring
publication_statusqueryNostringReturn a station only if it matches the requested publication state. Public requests may only use 'published'.
Response (200)
{
  "slug": "",
  "section": "",
  "title": "",
  "task": "",
  "prompts": {
    "station_name": "",
    "case_synopsis": ""
  },
  "lead": "",
  "key_points": [
    ""
  ],
  "persona": {
    "name": "",
    "age": 0,
    "affect": "",
    "communication_style": "",
    "goals_concerns": [
      ""
    ],
    "nonverbal_cues": [
      ""
    ],
    "do_not_volunteer": [
      ""
    ]
  },
  "guardrails": {
    "forbidden_topics": [
      ""
    ],
    "mandatory_disclosures": [
      ""
    ],
    "handoff_triggers": [
      ""
    ],
    "privacy_rules": [
      ""
    ],
    "out_of_scope_responses": [
      ""
    ]
  },
  "rubric": {
    "marking_sheet_title": "",
    "overall_scale": [
      ""
    ],
    "domain_rating_scale": [
      ""
    ],
    "rating_to_score": {},
    "competency_items": [
      {
        "label": "",
        "weight": 0.0,
        "scoring_mode": "",
        "critical": true,
        "hints": [
          ""
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 0,
        "title": "",
        "details": ""
      }
    ]
  },
  "evaluation": {
    "version": ""
  },
  "sources": {
    "pdf": ""
  },
  "station_meta": {
    "topic": "",
    "time_limit_minutes": 0,
    "setting": "",
    "expected_diagnosis": "",
    "station_category": ""
  },
  "publication": {
    "status": "published",
    "reason": "",
    "expert_reviewed": true
  },
  "health": {
    "issues": [
      ""
    ],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": true,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
PUT/stations/{slug}

Update Station

Parameters
NameInRequiredTypeDescription
slugpathYesstring
Request payload (application/json)
{
  "slug": "",
  "section": "",
  "title": "",
  "task": "",
  "prompts": {
    "station_name": "",
    "case_synopsis": ""
  },
  "lead": "",
  "key_points": [
    ""
  ],
  "persona": {
    "name": "",
    "age": 0,
    "affect": "",
    "communication_style": "",
    "goals_concerns": [
      ""
    ],
    "nonverbal_cues": [
      ""
    ],
    "do_not_volunteer": [
      ""
    ]
  },
  "guardrails": {
    "forbidden_topics": [
      ""
    ],
    "mandatory_disclosures": [
      ""
    ],
    "handoff_triggers": [
      ""
    ],
    "privacy_rules": [
      ""
    ],
    "out_of_scope_responses": [
      ""
    ]
  },
  "rubric": {
    "marking_sheet_title": "",
    "overall_scale": [
      ""
    ],
    "domain_rating_scale": [
      ""
    ],
    "rating_to_score": {},
    "competency_items": [
      {
        "label": "",
        "weight": 0.0,
        "scoring_mode": "",
        "critical": true,
        "hints": [
          ""
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 0,
        "title": "",
        "details": ""
      }
    ]
  },
  "evaluation": {
    "version": ""
  },
  "sources": {
    "pdf": ""
  },
  "station_meta": {
    "topic": "",
    "time_limit_minutes": 0,
    "setting": "",
    "expected_diagnosis": "",
    "station_category": ""
  },
  "publication": {
    "status": "published",
    "reason": "",
    "expert_reviewed": true
  },
  "health": {
    "issues": [
      ""
    ],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": true,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (200)
{
  "slug": "",
  "section": "",
  "title": "",
  "task": "",
  "prompts": {
    "station_name": "",
    "case_synopsis": ""
  },
  "lead": "",
  "key_points": [
    ""
  ],
  "persona": {
    "name": "",
    "age": 0,
    "affect": "",
    "communication_style": "",
    "goals_concerns": [
      ""
    ],
    "nonverbal_cues": [
      ""
    ],
    "do_not_volunteer": [
      ""
    ]
  },
  "guardrails": {
    "forbidden_topics": [
      ""
    ],
    "mandatory_disclosures": [
      ""
    ],
    "handoff_triggers": [
      ""
    ],
    "privacy_rules": [
      ""
    ],
    "out_of_scope_responses": [
      ""
    ]
  },
  "rubric": {
    "marking_sheet_title": "",
    "overall_scale": [
      ""
    ],
    "domain_rating_scale": [
      ""
    ],
    "rating_to_score": {},
    "competency_items": [
      {
        "label": "",
        "weight": 0.0,
        "scoring_mode": "",
        "critical": true,
        "hints": [
          ""
        ]
      }
    ],
    "feedback_checklist": [
      {
        "id": 0,
        "title": "",
        "details": ""
      }
    ]
  },
  "evaluation": {
    "version": ""
  },
  "sources": {
    "pdf": ""
  },
  "station_meta": {
    "topic": "",
    "time_limit_minutes": 0,
    "setting": "",
    "expected_diagnosis": "",
    "station_category": ""
  },
  "publication": {
    "status": "published",
    "reason": "",
    "expert_reviewed": true
  },
  "health": {
    "issues": [
      ""
    ],
    "has_rubric": true,
    "has_key_points": true,
    "has_persona": true,
    "has_guardrails": true,
    "has_tuning": true,
    "simulation_ready": true,
    "evaluation_ready": true,
    "publishable": true
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/stations/{slug}/archive-url

Get Station Archive Url

Parameters
NameInRequiredTypeDescription
slugpathYesstring
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/stations/{slug}/revisions

List Station Revisions

Parameters
NameInRequiredTypeDescription
slugpathYesstring
Response (200)
{
  "items": [
    {
      "id": 1297,
      "slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse",
      "revision_number": 3,
      "action": "update",
      "created_at": "2026-04-11T12:41:56.883672Z",
      "payload": {
        "slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse"
      }
    }
  ],
  "total": 1
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/stations/{slug}/revisions/{revision_id}/rollback

Rollback Station Revision

Parameters
NameInRequiredTypeDescription
slugpathYesstring
revision_idpathYesinteger
Response (200)
{
  "slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse",
  "section": "Psychiatry",
  "title": "Acute Presentation of Psychosis in the Context of Cannabis Misuse",
  "task": "Review the mental state findings and immediate risks.",
  "prompts": {
    "station_name": "Acute Presentation of Psychosis in the Context of Cannabis Misuse",
    "case_synopsis": "Candidate explores acute psychosis symptoms and immediate management."
  },
  "lead": "Acute psychosis",
  "key_points": [],
  "persona": {
    "name": "Patient",
    "age": 0,
    "affect": "neutral",
    "communication_style": "brief",
    "goals_concerns": [],
    "nonverbal_cues": [],
    "do_not_volunteer": []
  },
  "guardrails": {
    "forbidden_topics": [],
    "mandatory_disclosures": [],
    "handoff_triggers": [],
    "privacy_rules": [
      "no real third-party identifiers"
    ],
    "out_of_scope_responses": []
  },
  "rubric": {
    "marking_sheet_title": "MARKING SHEET",
    "overall_scale": [
      "SEVERE FAIL",
      "FAIL",
      "BORDERLINE FAIL",
      "BORDERLINE PASS",
      "PASS",
      "EXCELLENT PASS"
    ],
    "domain_rating_scale": [
      "0",
      "1",
      "2",
      "3",
      "4"
    ],
    "rating_to_score": {
      "0": 0.0,
      "1": 0.25,
      "2": 0.5,
      "3": 0.75,
      "4": 1.0
    },
    "competency_items": [],
    "feedback_checklist": []
  },
  "evaluation": {
    "version": "ev-1.0.0"
  },
  "sources": {
    "pdf": "NEWSTATIONS.pdf"
  },
  "station_meta": {
    "topic": "Acute psychosis",
    "time_limit_minutes": 7,
    "setting": "Clinic",
    "expected_diagnosis": "Working diagnosis",
    "station_category": "management"
  }
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}

System

GET/admin/provider-defaults

Get Provider Defaults

Response (200)
{
  "defaults": {
    "asr": {
      "category": "asr",
      "provider_name": "azure",
      "created_at": "2026-04-06T09:00:03.961692Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    },
    "nlp": {
      "category": "nlp",
      "provider_name": "openai",
      "created_at": "2026-04-06T09:00:03.961709Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    },
    "tts": {
      "category": "tts",
      "provider_name": "polly",
      "created_at": "2026-04-06T09:00:03.961714Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    }
  },
  "supported": {
    "asr": [
      "faster_whisper",
      "whisper",
      "openai",
      "deepgram",
      "elevenlabs",
      "azure",
      "aws_transcribe_medical",
      "parakeet_stream",
      "multitalker_parakeet"
    ],
    "nlp": [
      "openai"
    ],
    "tts": [
      "elevenlabs",
      "azure",
      "deepgram",
      "openai",
      "polly"
    ]
  }
}
Response (401)
{
  "detail": "Invalid API key."
}
PUT/admin/provider-defaults

Update Provider Defaults

Request payload (application/json)
{
  "asr_provider": "faster_whisper",
  "nlp_provider": "",
  "tts_provider": "elevenlabs",
  "actor": "",
  "note": "",
  "source": ""
}
Response (200)
{
  "defaults": {
    "asr": {
      "category": "asr",
      "provider_name": "azure",
      "created_at": "2026-04-06T09:00:03.961692Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    },
    "nlp": {
      "category": "nlp",
      "provider_name": "openai",
      "created_at": "2026-04-06T09:00:03.961709Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    },
    "tts": {
      "category": "tts",
      "provider_name": "polly",
      "created_at": "2026-04-06T09:00:03.961714Z",
      "updated_at": "2026-04-11T12:42:27.419272Z"
    }
  },
  "supported": {
    "asr": [
      "faster_whisper",
      "whisper",
      "openai",
      "deepgram",
      "elevenlabs",
      "azure",
      "aws_transcribe_medical",
      "parakeet_stream",
      "multitalker_parakeet"
    ],
    "nlp": [
      "openai"
    ],
    "tts": [
      "elevenlabs",
      "azure",
      "deepgram",
      "openai",
      "polly"
    ]
  },
  "updated": true
}
Response (401)
{
  "detail": "Invalid API key."
}
Response (422)
{
  "detail": [
    {
      "loc": [
        "body",
        "nlp_provider"
      ],
      "msg": "Input should be 'openai'",
      "type": "literal_error"
    }
  ]
}
GET/admin/provider-defaults/audit

List Provider Default Audit

Parameters
NameInRequiredTypeDescription
limitqueryNointeger
Response (200)
{
  "items": [
    {
      "id": 0,
      "category": "asr",
      "old_provider_name": "",
      "new_provider_name": "",
      "actor": "",
      "note": "",
      "source": "",
      "payload": {},
      "created_at": ""
    }
  ],
  "total": 0
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/health

Health

Response (200)
{}
GET/health/release

Health Release

Response (200)
{
  "service": "medicaitutor-api",
  "environment": "staging",
  "version": "0.1.0",
  "release_at": "2026-04-20 06:16 WAT",
  "image_uri": "757052694893.dkr.ecr.us-east-1.amazonaws.com/medicaitutor:a1e8233afcc2d65ae5b826bb982d890207794119",
  "source_sha": "a1e8233afcc2d65ae5b826bb982d890207794119"
}
GET/healthz

Healthz

Response (200)
{}
GET/metrics

Metrics

Response (200)
{}
GET/metrics/prometheus

Metrics Prometheus

Response (200)
{}
POST/providers/benchmark

Run Provider Benchmark

Parameters
NameInRequiredTypeDescription
smokequeryNoboolean
Response (200)
{
  "environment": {
    "audio_fixture": "benchmark_synthetic.wav",
    "station_slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse"
  },
  "ranking": {
    "asr": [
      {
        "latency_ms": 0.03,
        "provider": "mock",
        "status": "ok"
      }
    ],
    "nlp": [
      {
        "latency_ms": 640.4,
        "provider": "openai",
        "status": "ok"
      }
    ],
    "tts": [
      {
        "latency_ms": 210.11,
        "provider": "polly",
        "status": "ok"
      }
    ]
  },
  "regressions": [],
  "results": {
    "asr": [
      {
        "detail": "mock provider",
        "latency_ms": 0.03,
        "provider": "mock",
        "provider_type": "asr",
        "ready": true,
        "status": "ok",
        "transcript_chars": 56,
        "transcript_preview": "mock transcript 32044 bytes from benchmark_synthetic.wav"
      }
    ],
    "nlp": [
      {
        "detail": {
          "configured": true
        },
        "latency_ms": 640.4,
        "output_provider": "openai",
        "provider": "openai",
        "provider_type": "nlp",
        "ready": true,
        "response_preview": "Hello. I'm Patient.",
        "status": "ok"
      }
    ],
    "tts": [
      {
        "bytes": 4096,
        "detail": {
          "configured": true
        },
        "latency_ms": 210.11,
        "provider": "polly",
        "provider_type": "tts",
        "ready": true,
        "status": "ok"
      }
    ]
  },
  "timestamp": "2026-04-11T12:58:10.118673+00:00"
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/providers/benchmark/history

Provider Benchmark History

Parameters
NameInRequiredTypeDescription
limitqueryNointeger
Response (200)
{
  "items": [
    {
      "environment": {
        "audio_fixture": "benchmark_synthetic.wav",
        "station_slug": "acute-presentation-of-psychosis-in-the-context-of-cannabis-misuse"
      },
      "ranking": {
        "asr": [],
        "nlp": [],
        "tts": []
      },
      "regressions": [],
      "results": {
        "asr": [],
        "nlp": [],
        "tts": []
      },
      "timestamp": "2026-04-11T12:58:10.118673+00:00"
    }
  ],
  "total": 1
}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/providers/status

Provider Status

Parameters
NameInRequiredTypeDescription
livequeryNoboolean
refreshqueryNoboolean
catalog_livequeryNoboolean
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/ready

Ready

Response (200)
{}

Tts

POST/tts/speak

Speak

Request payload (application/json)
{
  "text": "",
  "voice": ""
}
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
GET/tts/{provider}

Get Provider Status

Parameters
NameInRequiredTypeDescription
providerpathYesstringTTS provider name. Options: elevenlabs, azure, deepgram, openai, polly.
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}
POST/tts/{provider}/speak

Speak Provider

Parameters
NameInRequiredTypeDescription
providerpathYesstringTTS provider name. Options: elevenlabs, azure, deepgram, openai, polly.
Request payload (application/json)
{
  "text": "",
  "voice": ""
}
Response (200)
{}
Response (422)
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": "",
      "ctx": {}
    }
  ]
}