API Guide

Dental Screening API

Score a patient's screening answers, let a doctor review and correct a result, generate a plain-language explanation, and answer follow-up questions about it in a chat.

Service is running

Before you start

FormatJSON in, JSON out. Set Content-Type: application/json on every POST/PATCH.
Who's askingEvery request should include one header: X-User-Role, set to either patient or doctor.
Why the role header? A patient only ever needs to see the category ("Most Likely", etc). A doctor also needs to see the score and the reasons behind it. Send patient or doctor and the API adjusts what it gives back automatically.

The three result levels

LevelMeaning
Less LikelyScreening did not find meaningful signs of this condition.
LikelySome signs were found — worth a closer look.
Most LikelyStrong signs were found — recommend follow-up.

Score a patient

Call this once the patient's (or the doctor's) answers are ready to submit. You don't need to send every possible answer — anything left out is treated as normal.

POST /api/v1/rule-engine/evaluate
Headers: Content-Type: application/json, X-User-Role: doctor

Request body

{
  "age": 8,
  "fields": {
    "overjet_mm": 7,
    "crossbite_assessment": "Anterior",
    "mouth_breathing_assessment": "Frequently"
  }
}

Response — save the id, you'll need it for every later step

{
  "id": "res_036afe8bbc3948b9952f2a01b29ba136",
  "age": 8,
  "visibility": "doctor",
  "diagnoses": [
    {
      "diagnosis_key": "tonsils_and_adenoids",
      "display_name": "Tonsils and Adenoids",
      "category": "Most Likely",
      "score": 7.0,
      "edited": false,
      "items": [
        { "label": "Does your child snore during sleep?", "value": "Frequently", "signal_type": "Confirmatory" }
      ]
    }
  ]
}

If the header said patient instead, each entry in "diagnoses" would only contain display_name, category, and edited — no score, no items.

Get a result again later

GET /api/v1/rule-engine/results/{id}
Headers: X-User-Role: patient  (or doctor)

Same response shape as scoring — use this to re-show a result on page load without re-submitting the form.

Doctor updates a result

Only works with X-User-Role: doctor — anything else is rejected.

PATCH /api/v1/rule-engine/results/{id}/corrections
Headers: Content-Type: application/json, X-User-Role: doctor

{
  "diagnosis_key": "tongue_tie",
  "category": "Likely",
  "edited_by": "dr_jane_doe"
}

The updated value is what everyone sees afterwards — including patients — the next time you fetch this result.

Get a plain-language explanation

No body needed — just the result's id in the address. This can take up to about 40 seconds, so show a loading state.

POST /api/v1/rule-engine/results/{id}/explanation
{
  "overview": "After reviewing the screening results...",
  "final": "After reviewing the screening results, it appears that several areas could benefit from a closer look...",
  "per_diagnosis": [
    { "name": "Tonsils and Adenoids", "category": "Most Likely", "explanation": "This was flagged because..." }
  ]
}

Show "final" as the main explanation. Once generated, re-fetch it any time without waiting again: GET /api/v1/rule-engine/results/{id}/explanation

Get treatment recommendations

No body needed — just the result's id in the address. This looks at whichever conditions came back Likely or Most Likely and suggests next steps from a fixed, clinic-approved list for that age group. Does not require an explanation to exist first — it only needs a scored result. If nothing was flagged, it returns an empty list instantly.

POST /api/v1/rule-engine/results/{id}/treatment-recommendation
{
  "recommendations": [
    {
      "variable_name": "tongue_tie_release",
      "name": "Tongue Tie release",
      "description": "A minor procedure (frenotomy or frenectomy) that releases a restrictive band of tissue under the tongue...",
      "justification": "The patient has multiple indicators of tongue tie, including speech therapy history and delayed language development..."
    }
  ]
}

Show each recommendation's "name" and "justification" together. Once generated, re-fetch it any time: GET /api/v1/rule-engine/results/{id}/treatment-recommendation

Chat about a result

Lets a user ask follow-up questions about an explanation. Generate the explanation first — chatting before that returns an error telling you to do so.

Start a chat (asks the first question too)

POST /api/v1/chat/init
{ "result_id": "res_036afe...", "question": "Why is this flagged?" }
{
  "chat_id": "chat_a1addf9ee40347f2b980020fd6fce456",
  "current_turn": { "question": "Why is this flagged?", "answer": "..." },
  "history": [ { "question": "...", "answer": "..." } ]
}

Ask another question (reuse the same chat_id)

POST /api/v1/chat/{chat_id}/messages
{ "question": "Is this urgent?" }

Resume a chat without asking a new question

GET /api/v1/chat/{chat_id}

Start over

Call "Start a chat" again with the same result_id — that begins a brand-new conversation. You can optionally delete the old one: DELETE /api/v1/chat/{chat_id}

If something goes wrong

Every error looks like this:

{ "error": { "code": "not_found", "message": "..." } }
error.codeWhat it meansWhat to do
forbiddenThis action needs the doctor role.Only show the action to doctor users.
not_foundThe id you sent doesn't exist.Double check the id you saved earlier.
conflictYou skipped a step (e.g. chatting before an explanation exists).Do the earlier step first — the message says which.
llm_unavailableThe AI explanation/chat service is temporarily down.Show a "try again" message; retry shortly.
validation_errorSomething in your request was missing or the wrong type.Check the field list for that request.