Endpoint Reference
Every v1 endpoint - method, path, headers, request/response schemas, and status codes
All endpoints are relative to the environment base URL (e.g. https://portal.your.md/api/v1). Unless stated otherwise, every request requires:
x-api-key: <api_key>
Authorization: Bearer <access_token> (upgraded token for assessment endpoints)
Content-Type: application/json (when a body is sent)
Error responses share a single format — see Errors, Limits & Timeouts.
Authentication & legal
POST /login
POST /loginExchange partner credentials for a session token. The only endpoint that does not need Authorization (the x-api-key header is still required).
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
partner_id | string | yes | Issued by Healthily |
secret | string | yes | Issued by Healthily |
Optional header: X-Language (default en-US; must be enabled for your partner — otherwise 400 "Language ... is not allowed").
Response 200:
{ "access_token": "eyJ...", "token_type": "bearer", "expires_in": 1860 }Errors: 400 missing fields · 401 bad credentials.
GET /terms
GET /termsFetch the legal content to display. Requires the initial token.
Response 200: content_version, messages[], checkbox_policies[], footer_policies[] — see Legal & Consent.
POST /terms
POST /termsRecord consent; returns the upgraded token.
Request body:
| Field | Type | Required |
|---|---|---|
content_version | string | yes — exactly as returned by GET /terms |
all_policies_accepted | boolean | yes — must be true to proceed |
Response 200: login_response (new token) + onboarding_response (age_question, gender_question, symptoms_question).
Consultation
POST /initial
POST /initialStart the assessment. Requires the upgraded token.
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
gender | string | yes | e.g. male, female (use ids from onboarding_response.gender_question) |
age | integer | yes | 18–125 |
initial_query | string | yes | free text, 3–500 characters |
Response 200: a ChatResponse with the first question.
POST /chat
POST /chatAnswer the current question; returns the next question (or a state where the report is available).
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | yes | The type of the question being answered: generic, symptom, symptoms, symptoms_choose_one, factor, health_background, or autocomplete |
included | string[] | see note | Choice ids the user selected |
excluded | string[] | see note | Choice ids the user saw but did not select |
At least one of included/excluded must be non-empty. Selections must come from the current question's choices and respect its constraints.min_selections/max_selections; otherwise 400.
Response 200: ChatResponse.
GET /chat
GET /chatReturn the current state (latest question or final state) without advancing the conversation. Use for UI restore after refresh/resume, for recovery after a timed-out POST /chat, and to re-read the final state after conversation_ended.
Response 200: ChatResponse. 404 if no conversation exists for this session yet.
POST /step-back
POST /step-backUndo the last answered question. Empty body.
Precondition: latest response had conversation_details.step_back_possible: true.
Response 200: ChatResponse re-rendering the previous question.
GET /report
GET /reportGenerate/fetch the final report.
Precondition: conversation_details.report_possible: true — calling earlier returns 400 ("Report generation not allowed at this point").
Response 200:
{
"conversation_details": { "conversation_ended": true, ... },
"report": { ... } // PatientReport — see Reports chapter
}See Reports for the full report schema.
Search
GET /symptom-search
GET /symptom-searchSymptom autocomplete, for driving a type-ahead search box during the autocomplete phase.
Query parameters:
| Param | Required | Constraints |
|---|---|---|
text | yes | 1–100 characters |
gender | no | filter results by gender relevance |
Response 200:
{
"user_facing_prefix": "also known as",
"autocomplete": [
{
"id": "assessment_C0231749",
"cui": "C0231749",
"dropdown_name": "Jaw clicking (Clicking noises from jaw)",
"display_name": "Jaw clicking",
"highlight": "Jaw <i>clicking</i> (Clicking noises from jaw)"
}
]
}dropdown_name— show in the suggestion list;highlightis the same string with the matched term wrapped in<i>…</i>for emphasis.display_name— show once selected (as a chip/tag).id— send this (notcui) as theincludedvalue of theautocompleteanswer.
This is a fast endpoint (server timeout 3 s) intended to be called on each keystroke (debounce client-side — remember the per-partner rate limits).
Content
GET /articles?ids=<id1>,<id2>,...
GET /articles?ids=<id1>,<id2>,...Fetch up to 20 full articles by id (ids come from report information_articles and condition articles).
Response 200: { "articles": [ ... ] } with full article content (paragraphs, images, contributors, metadata).
GET /article/{id}
GET /article/{id}Fetch a single full article.
Response 200: the article object. 404 if unknown.
Analytics & feedback (optional but recommended)
POST /analytics
POST /analyticsFire-and-forget UI interaction events (helps Healthily and you analyse funnel behaviour).
{ "event": "accordion_item_opened", "value": { "gender": "male", "age": "31" } }event(string, required);valueoptional — any JSON (string/array/object), total size limited (~1000 characters; oversized payloads are rejected).
Response 200 (accepted asynchronously).
POST /analytics/feedback
POST /analytics/feedbackEnd-of-assessment user feedback.
{ "stars": 5, "labels": ["Fast", "Responsive"], "message": "I liked it" }| Field | Constraints |
|---|---|
stars | required, 1–5 |
labels | optional, max 8 strings |
message | optional, max 1000 characters |
ChatResponse shape
Returned by POST /initial, POST /chat, GET /chat, and POST /step-back. Empty/null fields are omitted.
{
"conversation_details": {
"step_back_possible": true,
"report_possible": false,
"scenario": "CONSULTATION_QUESTIONS",
"conversation_ended": false,
"symptoms_summary": {
"selected": [ { "cui": "C0018621", "name": "Earache" } ],
"excluded": [ { "cui": "C0006325", "name": "Bruxism" } ],
"ignored": []
},
"progress": { "stage": "Symptoms Assessment", "percentage": 30 }
},
"question": {
"type": "symptoms",
"id": "q-123",
"messages": [
{ "value": "Do you have any of the following?", "type": "HEADER", "format": "PLAIN_TEXT" }
],
"choices": [
{ "id": "C0041105", "text": "Ringing in the ears" },
{ "id": "C0018681", "text": "Headache",
"symptom_definition": { "value": "Pain anywhere in the head...", "type": "PARAGRAPH", "format": "PLAIN_TEXT" } }
],
"constraints": { "min_selections": 0, "max_selections": 4 }
},
"report": null
}question— present while there is something to ask. See Questions & Answers for every question type and how to render it.report— present when the flow terminates directly with a report payload; in the normal consultation flow you fetch the report viaGET /reportoncereport_possibleistrue.debug_messages— internal diagnostics; only present for partners with debug enabled. Never show to end users.
