> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hi-doctor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Connect the Hi Doctor MCP connector at https://mcp.hi-doctor.ai/mcp. Add this URL as a remote HTTP MCP server in your client, then sign in and authorise access through OAuth. Setup: https://docs.hi-doctor.ai/mcp/connect.
>
> The REST API base is https://api.hi-doctor.ai. Hi Doctor Docs at https://docs.hi-doctor.ai/mcp searches documentation; connect to the product endpoint above for patient tools.
>
> Hi Doctor operates across the whole European Union: residents of all 27 EU member states can use the service, and every consultation is reviewed by an EU-licensed doctor. Every consultation is reviewed in full by an EU-licensed doctor, who decides on medical grounds only. No algorithm, assistant or API call approves a consultation, issues a prescription or changes a dose. The service is available in eight languages: English, Spanish, German, Dutch, Italian, French, Portuguese and Chinese.
>
> ## Rules an assistant must follow
>
> - Never invent, assume or infer a medical answer. "I don't know" is a real answer: ask the patient to pick the closest option rather than guessing for them.
> - Never give medical advice and never suggest a medication or a dose. An EU-licensed doctor decides those.
> - If a questionnaire returns `ineligible`, say so plainly. Do not retry with altered answers.
> - Before anything that costs money or cancels a plan, state exactly what will happen and get an explicit yes.
> - Cancellation takes effect at the **end of the paid period**, not immediately. Say so.
> - An early order is irreversible: it creates the renewal consultation immediately and uses up the period's one early order. Explain that and get an explicit yes.
> - Prescription and invoice links are medical documents. A `signed_redirect` link opens without signing in: give it only to the patient, never repeat it anywhere shared.
> - Send option `value`s, not the labels shown to the patient. Labels are translated, values are stable.
> - Never ask for card details. Payment happens on Stripe's hosted checkout via a link.
> - On severe or sudden symptoms (chest pain, difficulty breathing, severe allergic reaction, suicidal thoughts), stop and direct the patient to their local emergency number.

# Answer a Hi Doctor questionnaire one question at a time

> Walk a patient through a consultation questionnaire one item at a time: current, answer, back, review and submit, with skips, sub-items and batches.

The guided questionnaire endpoints let your application ask a Hi Doctor consultation questionnaire one question at a time, while Hi Doctor applies the branching, validation and progress rules for you. Every call needs a patient bearer token and works on the same saved draft as `PATCH .../progress/`, so a patient can start in your application and finish in the Hi Doctor web app.

Last reviewed: 2026-11-04

[Hi Doctor operates across the whole European Union](https://docs.hi-doctor.ai/essentials/regions.md): residents of all 27 EU member states can use the service, and every consultation is reviewed by an EU-licensed doctor.

| Method | Path under `/v1/users/questionnaires/{slug}/` | What it does |
| - | - | - |
| `GET` | `current/` | Returns the next unanswered item that applies |
| `POST` | `answer/` | Saves one answer, one sub-item, a skip or a batch, then returns the next item |
| `POST` | `back/` | Returns an answered item for editing; changes nothing |
| `GET` | `review/` | Lists every applicable answer, what is missing and whether the draft is ready |

Get the `slug` from the categories list (see [questionnaire endpoints](https://docs.hi-doctor.ai/api-reference/tags/questionnaires.md)). All four endpoints return `Cache-Control: no-store`; do not cache their responses.

## How does the guided loop work?

**1. Read the current item**

`GET current/` returns `question` (a descriptor, or `null` when nothing is pending), `complete`, `status`, `progress` and an opaque `revision`. Show `question.title`, `help`, `options` and `constraints`.

**2. Send the answer**

`POST answer/` with `question_key`, the value and `expected_revision`. The response is the next item, in the same shape as `current`, with a new `revision`.

**3. Let the patient go back**

`POST back/` returns the previous answered item with its `saved_answer`. To change it, send the new value to `answer/`.

**4. Review**

When `complete` is `true`, `GET review/` shows every applicable answer with a readable `answer_label`. Check `ready_to_submit` and `missing_required_keys`.

**5. Submit**

Read `GET progress/` and post its `progress.data` to `POST submit/` as `{"data": [...]}`. The guided endpoints never submit on their own.

> [!NOTE]
>
> The questionnaire is answered by the patient. Your application shows the questions and passes on what the patient enters. Never fill in clinical answers from inference, defaults or another person's data.

## How do you read the current item?

```bash curl theme={null}
curl --silent --show-error --max-time 30 \
  "$HD_BASE_URL/v1/users/questionnaires/weight-loss/current/?locale=en" \
  -H "Authorization: Bearer $HD_ACCESS_TOKEN" \
  -H "X-Brand-Slug: hi-doctor" \
  -H "Accept: application/json"
```

```python Python theme={null}
import os
import requests

base = os.environ["HD_BASE_URL"].rstrip("/")
session = requests.Session()
session.headers.update({
    "Authorization": "Bearer " + os.environ["HD_ACCESS_TOKEN"],
    "X-Brand-Slug": "hi-doctor",
    "Accept": "application/json",
})
flow_url = base + "/v1/users/questionnaires/weight-loss/"

current = session.get(flow_url + "current/", params={"locale": "en"}, timeout=30)
current.raise_for_status()
flow = current.json()
question = flow["question"]  # None when nothing is pending
```

```javascript JavaScript theme={null}
const base = process.env.HD_BASE_URL.replace(/\/$/, "");
const headers = {
  Authorization: `Bearer ${process.env.HD_ACCESS_TOKEN}`,
  "X-Brand-Slug": "hi-doctor",
  Accept: "application/json",
};
const flowUrl = `${base}/v1/users/questionnaires/weight-loss/`;

const response = await fetch(`${flowUrl}current/?locale=en`, {
  headers,
  redirect: "error",
  signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`current HTTP ${response.status}`);
const flow = await response.json();
const question = flow.question; // null when nothing is pending
```

A shortened response:

```json theme={null}
{
  "question": {
    "question_key": "weight_loss-02-gender",
    "type": "radio",
    "title": "What is your biological sex?",
    "options": [{"value": "male", "label": "Male"}, {"value": "female", "label": "Female"}],
    "constraints": {"required": true},
    "saved_answer": null
  },
  "complete": false,
  "status": "in_progress",
  "revision": "r-8f3c2a91",
  "progress": {"answered": 1, "total_applicable": 13, "percent": 7.7},
  "reusable_prefill": null
}
```

`reusable_prefill` can offer an answer the patient gave in an earlier consultation. Show it as a suggestion and send it only after the patient confirms it; until then the question is not answered. `current` never creates or changes saved progress.

## Send an answer

Use the value shape that matches the question:

| Question | `value` |
| - | - |
| Single choice | A scalar, such as `"FRA"` or `"male"` |
| Multiple choice | An array of option values |
| Input or grid question | A map of field names to values |
| Upload | The ID of a health document the patient already uploaded with `POST /v1/users/documents/` (see [Uploads and downloads](https://docs.hi-doctor.ai/api-reference/uploads-downloads.md)) |

`values` is accepted as an alias of `value`; send only one of them.

```bash curl theme={null}
curl --silent --show-error --max-time 30 --request POST \
  "$HD_BASE_URL/v1/users/questionnaires/weight-loss/answer/" \
  -H "Authorization: Bearer $HD_ACCESS_TOKEN" \
  -H "X-Brand-Slug: hi-doctor" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"question_key": "weight_loss-02-gender", "value": "male", "expected_revision": "r-8f3c2a91"}'
```

```python Python theme={null}
answer = session.post(flow_url + "answer/", json={
    "question_key": question["question_key"],
    "value": "male",  # the value the patient chose
    "expected_revision": flow["revision"],
}, timeout=30)
if answer.status_code == 409:
    flow = session.get(flow_url + "current/", timeout=30).json()  # re-read, then ask again
else:
    answer.raise_for_status()
    flow = answer.json()
```

```javascript JavaScript theme={null}
const answer = await fetch(`${flowUrl}answer/`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    question_key: question.question_key,
    value: "male", // the value the patient chose
    expected_revision: flow.revision,
  }),
  redirect: "error",
  signal: AbortSignal.timeout(30000),
});
if (answer.status === 409) {
  // Another write changed the draft: re-read current before asking again.
} else if (!answer.ok) {
  throw new Error(`answer HTTP ${answer.status}`);
}
const next = answer.ok ? await answer.json() : null;
```

A full answer to a question must leave all of that question's required fields populated, including any follow-ups the answer opens. Otherwise the request returns `400` with `code: "required"` and nothing is saved. Other questions can stay unanswered.

### Skip an optional question

Optional questions stay in order. Once the required answers are valid, `complete` can be `true` while `question` still holds an unanswered optional item. Offer it, or move past it explicitly:

```json theme={null}
{"question_key": "weight_loss-allergies", "skip": true}
```

Send the literal `true` and no value. Only optional questions can be skipped. A skip counts as handled in `progress` and is accepted by submit. Answering the question later replaces the skip. Skipping an optional question removes its answer and does not open its follow-ups.

### Answer one grid item or follow-up

Grids are served one row at a time, and follow-ups an answer opens appear as their own items. Their descriptors carry the address to answer them:

* A grid item has `values_key`. Send it with the value under that key:

  ```json theme={null}
  {"question_key": "mood-grid", "values_key": "interest", "value": {"interest": 0}}
  ```

* A follow-up has `option_input_key` (conditional fields use the form `__cf_<fieldName>`) and `parent_question_title`. Send the value in `option_inputs`:

  ```json theme={null}
  {"question_key": "weight_loss-currentmedications", "option_input_key": "other", "option_inputs": {"other": "Details entered by the patient"}}
  ```

Including the selector asks for that one item only, and its value is required. Field maps merge with the values already saved.

### Send a batch

Send several answers in one request with `answers`:

```json theme={null}
{
  "answers": [
    {"question_key": "weight_loss-01-country", "value": "ITA"},
    {"question_key": "weight_loss-02-gender", "value": "male"}
  ],
  "expected_revision": "r-8f3c2a91"
}
```

The batch is atomic: if any entry is invalid, nothing is saved. Applicability is checked against the final merged result, whatever the order of the entries, so a batch cannot answer a question that another entry in the same batch makes inapplicable. Do not mix `answers` with a single-answer `question_key`.

## Revisions and conflicts

`revision` is a token for the content of the draft. Send it back as `expected_revision`. If another write changed the draft in between (another tab, the web app, or a parallel request), the answer is rejected:

```json theme={null}
{"code": "stale_revision", "message": "Progress changed. Fetch current and retry.", "revision": "r-51d0e7b4"}
```

The response is HTTP `409` and nothing was saved. Read `current/` or `review/` again, show the patient the up-to-date question, and send the answer with the new revision. Saving data that has not changed keeps the same revision, so repeating an identical answer is harmless. `back/` never changes the revision.

> [!NOTE]
>
> `answer/` does not take an `Idempotency-Key`. `expected_revision` is what makes a retry safe here. See [safe retries](https://docs.hi-doctor.ai/api-reference/idempotency.md) for the endpoints that do.

## What happens when an answer closes a branch?

Changing an answer can make other questions no longer apply. Their answers, and any answers that depend on them, are removed from the draft. The response lists what was removed:

```json theme={null}
{
  "dropped_question_keys": ["weight_loss-previous-medication-dose-wegovy"],
  "dropped_option_inputs": [{"question_key": "weight_loss-currentmedications", "option_input_keys": ["other"]}]
}
```

`dropped_question_keys` names whole answers that were removed. `dropped_option_inputs` names follow-up values removed while their parent answer was kept. Tell the patient when an answer they gave has been cleared.

## How does the patient go back?

`POST back/` with an empty JSON object returns the last answered item before the first pending one, or the final answered item when everything is handled. Send `question_key` to pick an answered question, and add `values_key` or `option_input_key` to pick one grid row or follow-up.

```json theme={null}
{"question_key": "weight_loss-01-country"}
```

| Situation | `question` | `moved_back` | `at_start` |
| - | - | - | - |
| No answers yet | The first item | `false` | `true` |
| You select the answered first item | The first item | `true` | `true` |
| You select a later answered item | That item | `true` | `false` |
| Empty questionnaire | `null` | `false` | `true` |

Selecting a question that has no answer, or that no longer applies, returns `400` with `code: "unanswered_question"`.

## How do you review and submit?

`GET review/` returns one row in `answers` per applicable question, in order, with `saved_answer`, a localized `answer_label` and `items` for grid rows and follow-ups. Each item has an `answered` flag and its `values_key` or `option_input_key`, so you can send the exact payload for anything still pending.

* `missing_required_keys` names parent questions that still need an answer. A question can have a saved selection and still be listed because one of its items is pending.
* `errors` reports invalid saved values, including values written earlier through `PATCH .../progress/`, and upload ownership problems.
* `ready_to_submit` uses the same checks as submit. Submit can still apply separate renewal and subscription rules.

To submit, read `GET progress/` and post its `progress.data` unchanged:

```json theme={null}
{"data": [{"question_key": "weight_loss-01-country", "value": "ITA"}]}
```

Submission returns `201` and sends the questionnaire for review by an EU-licensed doctor. `POST submit/` accepts an [`Idempotency-Key`](https://docs.hi-doctor.ai/api-reference/idempotency), so a timed-out submission can be retried without creating a second one. Visiting the guided endpoints after a submission starts a fresh draft.

> [!WARNING]
>
> Clinical eligibility is decided by the questionnaire rules and the reviewing doctor. An answer can be rejected with `code: "clinical_ineligibility"`. Show the message to the patient and do not retry with changed answers.

## Which languages are supported?

All four endpoints accept `locale=en`, `locale=es` or `locale=zh`; any other value falls back to English. On `POST` requests the locale can also be sent in the body; the query parameter wins. Otherwise the patient's account language is used, with English as the fallback. The locale changes titles, labels and help text only. Question keys and option values stay the same. The Hi Doctor service is available in [eight languages](https://docs.hi-doctor.ai/essentials/regions.md); these guided endpoints serve the three listed here.

## Renewals

For a renewal, send the same `renewal_origin`, `renewal_changed`, `treatment_id`, `dosage` and, where it applies, `consultation_id` on every guided call and on submit. `GET` requests take them as query parameters and `POST` requests in the JSON body. Dosage eligibility is still checked at submit.

## Errors

Validation errors return `400` with a code and a message for each field. They never echo the patient's values:

```json theme={null}
{"errors": {"question_key": [{"code": "unknown_question", "message": "Choose a question from this questionnaire."}]}}
```

| Code | Meaning |
| - | - |
| `invalid_request` | The body is not a JSON object, or mixes a single answer with `answers` |
| `unknown_question` | The key is not part of this questionnaire |
| `unknown_field` | A field, score or follow-up that the question does not accept |
| `duplicate_question` | The same question appears twice in a batch |
| `inapplicable_question` | The question does not apply given the other answers |
| `unanswered_question` | `back/` selected a question with no answer |
| `invalid_answer` | The value does not fit the question |
| `required` | A required field of the question, or of an opened follow-up, is missing |
| `clinical_ineligibility` | The answer rules out this consultation |

A body that is not JSON returns `415`. An unknown category slug returns `400` with a `detail` message, and a category that is not enabled for the brand returns `403` with `code: "category_not_enabled_for_brand"`. A stale `expected_revision` returns `409`, as described above. See [errors and recovery](https://docs.hi-doctor.ai/api-reference/errors.md) for `401` and `429` handling.

## Frequently asked questions

### Do I need to implement the questionnaire branching myself?

No. The guided endpoints return the next question that applies and remove answers that stop applying. Your application only shows each question and sends the patient's answer.

### Can a patient start in my app and finish on hi-doctor.ai?

Yes. The guided endpoints and the Hi Doctor web app work on the same saved draft, so the patient can continue in either place.

### What does a 409 stale revision response mean?

Another write changed the draft after you read it, so nothing was saved. Read the current item again, show the patient the up-to-date question and send the answer with the new revision.

### Is it safe to retry an answer after a timeout?

Yes, when you send the same expected revision. A repeated identical answer keeps the same revision, and a stale one is rejected without saving.

### Can my application fill in answers for the patient?

No. The questionnaire is answered by the patient. Never fill in clinical answers from inference, defaults or another person's data.

### What happens if an answer rules out the consultation?

The answer is rejected with the clinical ineligibility code. Show the message to the patient and do not retry with changed answers; eligibility is a clinical outcome.

### Does the guided flow submit the questionnaire?

No. The guided endpoints never submit. When the review shows the draft is ready, post the saved progress data to the submit endpoint.

## Next steps

* [Questionnaire endpoints](https://docs.hi-doctor.ai/api-reference/tags/questionnaires.md): Every questionnaire operation, including categories, schema and submit.
* [Safe retries](https://docs.hi-doctor.ai/api-reference/idempotency.md): Submit once, even after a timeout, with Idempotency-Key.
* [Uploads and downloads](https://docs.hi-doctor.ai/api-reference/uploads-downloads.md): Upload the documents and photos a questionnaire asks for.
* [MCP workflows](https://docs.hi-doctor.ai/mcp/workflows.md): The same loop run by an AI assistant through the Hi Doctor connector.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.