# Hi Doctor integration documentation Hi Doctor connects the patient's questionnaire and account to review by an EU-licensed doctor, prescription handling when appropriate and follow-up. The patient obtains medicine from a pharmacy and pays it separately. Integrate supported patient flows through account-authorised MCP or the patient REST API. This English bundle includes selected integration guidance. It excludes the health library, blog bodies and operation schema dumps. Follow the linked specification for endpoint details. ## Values and current service Our values are access to care, a real doctor every time, care that fits people's lives, fair and clear prices, fewer barriers across borders, and honest medical answers. Every consultation is read by an EU-licensed doctor. Medical decisions remain with that doctor. Sometimes the right next step is in-person care. See the [manifesto](https://hi-doctor.ai/manifesto). ## Business integration Businesses can build supported patient experiences with the account-authorised MCP connector or patient REST API. The brand header is not a credential and gives no permission to view other patients. No API keys or enterprise SLA are promised. Authorised responses may contain health data, so the integrator must establish the appropriate legal basis, safeguards and patient notices. Start with [integration options](https://docs.hi-doctor.ai/businesses/integration-options.md) and [data and consent](https://docs.hi-doctor.ai/businesses/data-and-consent.md), or [discuss your integration](mailto:hello@hi-doctor.ai). ## Hi Doctor for AI agents: MCP connector and REST API Source: https://docs.hi-doctor.ai/ai/index.md How AI agents use Hi Doctor: the product MCP connector, the docs search MCP and the patient REST API, with ten safety rules. AI agents work with Hi Doctor through the product MCP connector at `https://mcp.hi-doctor.ai/mcp` or the patient REST API at `https://api.hi-doctor.ai`. Both act only with the patient's own sign-in and approved permissions, and an EU-licensed doctor makes every medical decision. Start with the agent quickstart, then follow the ten safety rules on this page. Last reviewed: 2026-11-04 ### What is Hi Doctor? Hi Doctor is an online medical consultation service. 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. The full country list is on [countries](https://docs.hi-doctor.ai/essentials/regions.md). Some treatments are limited to specific countries by local rules. Every consultation is reviewed in full by an EU-licensed doctor, who decides on medical grounds only. The full statement is on [safety](https://docs.hi-doctor.ai/essentials/safety.md). If the doctor prescribes, the patient receives a prescription they can use at a pharmacy: an electronic prescription where the pharmacy's country supports it, otherwise a signed prescription document. [How it works](https://docs.hi-doctor.ai/essentials/how-it-works.md) describes the whole path. ### Which interface should an agent use? | Interface | Endpoint | Use it for | | --- | --- | --- | | Hi Doctor product MCP | `https://mcp.hi-doctor.ai/mcp` | An assistant acting for one signed-in patient. Tools depend on the scopes the patient approves. | | Hi Doctor docs search MCP | `https://docs.hi-doctor.ai/mcp` | Searching the public documentation. It has no access to any patient account. | | Patient REST API | `https://api.hi-doctor.ai` | Your own application, with a patient JWT access token and the `X-Brand-Slug: hi-doctor` header. | MCP OAuth tokens and REST JWTs are different credentials: never send one to the other interface. If you are new to MCP, read [what MCP is](https://docs.hi-doctor.ai/ai/what-is-mcp.md). To compare the two for a product, read [integration options](https://docs.hi-doctor.ai/businesses/integration-options.md). ### How does an agent start? 1. Read the public [connection instructions](https://docs.hi-doctor.ai/mcp/connect.md) and complete the patient's sign-in and consent. 2. List the tools the connection offers. The catalog depends on the approved scopes. 3. Make the smallest read that answers the patient's request, for example the profile. The [agent quickstart](https://docs.hi-doctor.ai/ai/quickstart.md) walks through this path and the questionnaire sequence. For application code, use the [REST quickstart](https://docs.hi-doctor.ai/api-reference/quickstart.md). Never ask a patient to paste a password, verification code or token into chat. ### Where must a person act? Some steps belong to the patient or the doctor and can never be done by an agent. | Step | Who acts and what the agent does | | --- | --- | | Account setup | The patient verifies their email and completes any identity step through the supported flow. | | Sign-in and consent | The patient signs in and approves permissions. The integrator separately sets up the lawful basis and safeguards for health data. | | Questionnaire | The patient gives truthful answers and confirms the read-back before submission. | | Photographs and documents | The patient takes the photograph or scan. An app that supports the connector's protected upload can send it through the upload tools; otherwise the patient uploads on the website. See [workflows](https://docs.hi-doctor.ai/mcp/workflows.md). | | Approval link | The patient opens the link, signs in on Hi Doctor and approves or declines the exact action. See [approval links](https://docs.hi-doctor.ai/mcp/approvals.md). | | Payment | The patient pays on Stripe's hosted page. A payment link is not a receipt. | | Medical review | An EU-licensed doctor reviews the consultation and makes the medical decision. Review happens after submission, not during the chat. | | Pharmacy | The patient collects any prescribed medicine from a pharmacy, which charges for it. Hi Doctor does not sell or ship medicine. | A submitted questionnaire or a checkout link does not mean treatment has been approved. ### Safety rules - 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. ### Key terms | Term | Meaning | | --- | --- | | Skill | The [capability skill](https://docs.hi-doctor.ai/skill.md): a public instruction file an agent can load. It describes the workflow and rules and grants no access or permissions. | | Scope | One permission the patient approves at sign-in, such as reading the profile. See [permissions](https://docs.hi-doctor.ai/mcp/permissions.md). | | Approval link | An `approval_url` a tool returns before a consequential action. The patient approves the exact action on Hi Doctor before the agent commits it. | | `signed_redirect` | A delivery mode for document links. The link opens without signing in, so treat it as confidential. An `authenticated_url` link needs the patient's own session instead. | | `Idempotency-Key` | A key sent with a write so a retry runs the write at most once. See [safe retries](https://docs.hi-doctor.ai/api-reference/idempotency.md). | ### How should an agent recover and handle sensitive data? If a write times out, its outcome is uncertain. On the endpoints that document it, resend the same `Idempotency-Key` or look the key up before doing anything else; the write then runs at most once. Elsewhere, read the current state with a documented safe operation, or ask support before repeating it. See the [error and retry guide](https://docs.hi-doctor.ai/api-reference/errors.md). Authorised responses can contain health records or document links that the integrator and its model provider can see. Use only the data needed for the task, and keep it out of shared logs and unrelated prompts. OAuth consent alone does not meet every requirement for processing health data. Read [MCP security](https://docs.hi-doctor.ai/mcp/security.md) and [data and consent](https://docs.hi-doctor.ai/businesses/data-and-consent.md). ### Machine-readable resources - [Task index](https://docs.hi-doctor.ai/llms.txt): small entry point with links to the main guides. - [Integration context](https://docs.hi-doctor.ai/llms-full.txt): selected English integration guidance, without the health library or blog. - [Capability skill](https://docs.hi-doctor.ai/skill.md): prerequisites, workflow and safety instructions. It grants no permissions. - [Public OpenAPI specification](https://docs.hi-doctor.ai/api-reference/openapi.json): request and response details. - [API changelog](https://docs.hi-doctor.ai/api-reference/changelog.md) and [MCP changelog](https://docs.hi-doctor.ai/mcp/changelog.md): changes to each interface. ### Frequently asked questions #### Can an AI agent prescribe medicine through Hi Doctor? No. 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. #### Which countries can patients use Hi Doctor from? 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. #### What is the difference between the product MCP and the docs search MCP? The product MCP acts on one patient's account after that patient signs in and approves permissions. The docs search MCP only searches public documentation and cannot read or change any account. #### Does installing the Hi Doctor skill give an agent access to an account? No. The skill is a set of instructions. Access comes only from the patient's own sign-in and the permissions they approve. #### Can an agent submit a questionnaire or cancel a plan on its own? No. Consequential tools return an approval link first. The patient signs in on Hi Doctor and approves the exact action, and only then can the agent commit it. #### What should an agent do when a questionnaire returns ineligible? Tell the patient plainly and explain the message the tool returned. Do not change the answers and submit again. #### Can an agent pay for a consultation? No. The patient opens a payment link and pays on Stripe's hosted page. An agent never asks for or handles card details. #### Should an agent use MCP or the REST API? Use the product MCP when an AI assistant acts for a patient in a chat. Use the REST API when you build your own application with its own screens and token handling. ### Next steps - [Agent quickstart](https://docs.hi-doctor.ai/ai/quickstart.md): connect, list tools, read the profile and guide a questionnaire. - [What is MCP?](https://docs.hi-doctor.ai/ai/what-is-mcp.md): the protocol behind the Hi Doctor connector, in plain terms. - [Workflows](https://docs.hi-doctor.ai/mcp/workflows.md): patient tasks step by step, including approval links. - [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md): how a patient approves an action before it runs. For a business integration beyond the documented patient flows, [discuss your requirements](mailto:hello@hi-doctor.ai). ## Agent quickstart: connect Hi Doctor MCP and guide a patient Source: https://docs.hi-doctor.ai/ai/quickstart.md Connect an agent to the Hi Doctor MCP connector, list tools, read a profile and guide a questionnaire through to an approved submission. This quickstart takes an AI agent from no connection to a submitted questionnaire on the Hi Doctor product MCP, `https://mcp.hi-doctor.ai/mcp`. The patient signs in, approves permissions and approves the submission; the agent only prepares each step. Read the [interface and safety guide](https://docs.hi-doctor.ai/ai/index.md) first, then follow the five stages below. Last reviewed: 2026-11-04 The docs search MCP at `https://docs.hi-doctor.ai/mcp` only searches public documentation and is not used here. For your own application on the REST API, follow the [API quickstart](https://docs.hi-doctor.ai/api-reference/quickstart.md) instead. ### How does an agent connect and authorise? 1. Read [connect](https://docs.hi-doctor.ai/mcp/connect.md) and [Streamable HTTP](https://docs.hi-doctor.ai/mcp/streamable-http.md). Use the unauthenticated challenge's `resource_metadata` URL to discover the protected resource and its authorisation server. 2. Read the authorisation server metadata. Use its deployed registration mechanism and PKCE with `S256`; do not assume client ID metadata documents are supported. 3. Let the patient sign in and approve the scopes. Request only the permissions the task needs, and never collect a password, email code or token in chat. 4. Send `initialize`, then `tools/list` with the issued MCP token. The client keeps tokens outside the model context. Never send a REST JWT to the product MCP or an MCP token to the REST API. ### What does tools/list return? The catalog depends on the scopes the patient approved, so always use the list and schemas returned for that connection. This excerpt shows two read-only tools with the field layout the server uses; descriptions are paraphrased and schemas shortened, so rely on the text your connection returns. ```json tools/list result (excerpt) { "tools": [ { "name": "hidoctor_profile_get", "title": "Profile get", "description": "Reads the signed-in account's profile.", "inputSchema": { "type": "object", "properties": {} }, "annotations": { "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false } }, { "name": "hidoctor_questionnaire_categories", "title": "Questionnaire categories", "description": "Lists the questionnaire categories and their slugs.", "inputSchema": { "type": "object", "properties": {} }, "annotations": { "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false } } ] } ``` The full catalog, with which tools need an approval link, is in the [patient tool reference](https://docs.hi-doctor.ai/mcp/tools/patient.md). ### What is a safe first read? For a patient who asks to check their account, call `hidoctor_profile_get` with `{}`. Show only the fields needed for the request and do not log the response. ### How does an agent guide a questionnaire? | Stage | Tool and action | Stop condition | | --- | --- | --- | | Select | `hidoctor_questionnaire_categories` lists the categories; the patient chooses one | Never choose a treatment or medicine for the patient | | Start or resume | `hidoctor_questionnaire_start` returns the next question and its options | Identity steps happen in the supported patient flow; photographs go through the protected upload or the website | | Answer | `hidoctor_questionnaire_answer` saves the patient's answer using option `value`s | Never infer missing medical information | | Review | `hidoctor_questionnaire_review` reads the answers back; `hidoctor_questionnaire_back` returns to a question the patient wants to change | Get explicit confirmation before submission | | Submit | `hidoctor_questionnaire_submit` prepares the submission and returns an `approval_url` | The patient approves on Hi Doctor before the agent commits | After the patient approves, poll `hidoctor_operation_get`, then call `hidoctor_questionnaire_submit` again with the same arguments, `mode: "commit"`, the `confirmation_id` and an `idempotency_key`. [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md) describes each state. Tool inputs come from the discovered schemas and returned identifiers, never from guessed field names or invented patient records. What happens after a submission, such as a payment link or cover by an active plan, is described in [workflows](https://docs.hi-doctor.ai/mcp/workflows.md); follow that page rather than assuming one fixed order. ### What does an ineligible result look like? A committed submission returns one of three outcomes: `submitted`, `ineligible` or `covered_by_active_plan`. An ineligible result carries a patient-safe `message` and nothing to pay. ```json Committed submission, ineligible { "outcome": "ineligible", "message": "Based on your answers, this treatment is not suitable for you." } ``` The message text above is illustrative; show the patient the message the tool returns. Tell the patient plainly, do not offer checkout, and never change answers to get a different outcome. `hidoctor_checkout_status` can finalise a paid session and create its consultation, so it is not a harmless polling call. After an uncertain write, follow the recovery guidance below before calling it. ### Who does what after submission? The patient verifies the account, authorises access, gives and confirms the medical answers, approves the submission and completes any payment. An EU-licensed doctor then reviews the consultation; the review happens after the chat, not during it. Any prescribed medicine is collected from a pharmacy, which charges for it separately. ### How should an agent handle errors? For `401`, renew authorisation through the client. For `403`, stop and resolve permissions. Never repeat a write blindly after a timeout or a `5xx`: reconcile first, which on the endpoints that document it means resending the same `Idempotency-Key` or looking the key up. See [errors and reconciliation](https://docs.hi-doctor.ai/api-reference/errors.md), [permissions](https://docs.hi-doctor.ai/mcp/permissions.md) and the ten [safety rules](https://docs.hi-doctor.ai/ai/index.md#safety-rules). ### Frequently asked questions #### Which MCP server should the agent connect to? Connect to the product MCP at https://mcp.hi-doctor.ai/mcp. The docs search MCP only searches public documentation and cannot act on an account. #### Why does tools/list show different tools for different patients? The catalog depends on the permissions the patient approved at sign-in. Always use the tools and schemas returned for that connection. #### Does submitting a questionnaire mean the treatment is approved? No. Submission sends the answers for review. An EU-licensed doctor reviews the consultation and makes the medical decision afterwards. #### Why does questionnaire submit return an approval link? Submission has consequences, so the patient approves the exact action on Hi Doctor first. The agent commits it only after the operation is approved. #### What should the agent do if the result is ineligible? Tell the patient plainly, show the returned message and stop this attempt. Do not change answers or retry to get a different result. #### Can the agent read the medical team conversation safely? Yes. hidoctor_messages_get_thread reads the conversation without creating it or marking it read. Sending a message uses an approval link. #### Can the agent reuse a REST API token with the MCP connector? No. MCP OAuth tokens and REST JWTs are separate credentials, and each works only with its own interface. ### Next steps - [Workflows](https://docs.hi-doctor.ai/mcp/workflows.md): payments, documents, messages and plans step by step. - [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md): states, expiry and commit rules. - [Patient tool reference](https://docs.hi-doctor.ai/mcp/tools/patient.md): every patient tool, its inputs and annotations. - [What is MCP?](https://docs.hi-doctor.ai/ai/what-is-mcp.md): the protocol explained in plain terms. ## REST API quickstart: sign in and make your first read Source: https://docs.hi-doctor.ai/api-reference/quickstart.md Sign a verified Hi Doctor patient in from your application, keep the JWT out of prompts and logs, and read the account profile over REST. This quickstart takes a patient application from an empty configuration to its first authenticated read of the Hi Doctor REST API. You set the API origin, sign a verified patient in with `POST /v1/users/token/`, and read the profile with the access token. Nothing here submits a consultation, pays or changes data. Last reviewed: 2026-11-04 An AI assistant acting for a patient should not follow this page. It uses the [agent quickstart](https://docs.hi-doctor.ai/ai/quickstart.md), where sign-in happens on hi-doctor.ai through the [MCP connector](https://docs.hi-doctor.ai/mcp/connect.md). ### What do you need before the first call? - A patient account that has completed [sign-up and email verification](https://docs.hi-doctor.ai/api-reference/signup.md), used with that patient's consent. - A server-side application that can keep secrets. The patient types their password into your application, never into a chat or a model prompt. - The API origin, `https://api.hi-doctor.ai`. To check that it is reachable, call `GET /v1/system/healthcheck/`; the root URL returns `404` by design. ### How do you make the first authenticated read? ### Set the API origin Configure `HD_BASE_URL=https://api.hi-doctor.ai` explicitly and refuse any other origin before you attach a token. ### Sign the patient in Send `POST /v1/users/token/` with `Content-Type: application/json`, `X-Brand-Slug: hi-doctor` and a JSON object containing `email` and `password`. Build the JSON with your client library, never by interpolating credentials into a shell command. ### Store the tokens securely A `200` response contains `access` and `refresh`. Keep them in secure server-side storage and supply the access token to your code as `HD_ACCESS_TOKEN`. Never log them. ### Read the profile Call `GET /v1/users/profile/me/` with `Authorization: Bearer` and the access token, plus `X-Brand-Slug: hi-doctor`. The examples below do exactly this. A sign-in request body looks like this: ```json { "email": "patient@example.com", "password": "" } ``` Wrong credentials return `401` with `code: "invalid_credentials"`. While the emailed verification code is still valid, sign-in with the registered password returns `401` with `code: "verification_required"`; once the code has expired the same request returns `invalid_credentials`. A Google-only account returns `401` with `code: "google_account"`. [Authentication](https://docs.hi-doctor.ai/api-reference/authentication.md) covers every sign-in failure and token refresh. ### Read your own profile Send `X-Brand-Slug: hi-doctor` on every request. It grants no permissions. A REST access token is not an MCP token. Each example is independent and performs one read. GET has no body; `Accept: application/json` requests JSON. For endpoints with a JSON body, also send `Content-Type: application/json`. ```bash curl : "${HD_BASE_URL:?Set the API origin explicitly}" : "${HD_ACCESS_TOKEN:?Supply a token securely}" case "$HD_BASE_URL" in https://api.hi-doctor.ai) ;; *) exit 1 ;; esac curl --silent --show-error --fail --max-time 15 \ --output /dev/null --write-out 'Profile HTTP %{http_code}\n' \ "$HD_BASE_URL/v1/users/profile/me/" \ -H "Authorization: Bearer $HD_ACCESS_TOKEN" \ -H "X-Brand-Slug: hi-doctor" \ -H "Accept: application/json" ``` ```javascript JavaScript (Node.js) const base = process.env.HD_BASE_URL; const token = process.env.HD_ACCESS_TOKEN; if (base !== "https://api.hi-doctor.ai" || !token) { throw new Error("Configure an approved API origin and its access token"); } const response = await fetch(`${base}/v1/users/profile/me/`, { headers: { Authorization: `Bearer ${token}`, "X-Brand-Slug": "hi-doctor", Accept: "application/json", }, redirect: "error", signal: AbortSignal.timeout(15000), }); if (!response.ok) throw new Error(`Profile HTTP ${response.status}`); if (!response.headers.get("content-type")?.includes("application/json")) { throw new Error("Unexpected profile response format"); } const profile = await response.json(); console.log("Profile loaded"); // Use profile in the application; do not log it. ``` ```python Python (standard library) import json import os import urllib.error import urllib.request base = os.environ["HD_BASE_URL"] token = os.environ["HD_ACCESS_TOKEN"] if base != "https://api.hi-doctor.ai" or not token: raise ValueError("Configure an approved API origin and its access token") class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None request = urllib.request.Request(base + "/v1/users/profile/me/", headers={ "Authorization": "Bearer " + token, "X-Brand-Slug": "hi-doctor", "Accept": "application/json", }) try: with urllib.request.build_opener(NoRedirect).open(request, timeout=15) as response: if response.headers.get_content_type() != "application/json": raise ValueError("Unexpected profile response format") profile = json.load(response) except urllib.error.HTTPError as error: raise RuntimeError(f"Profile HTTP {error.code}") from None print("Profile loaded") # Use profile in the application; do not log it. ``` Success is HTTP `200` with one account object. `401` requires authentication recovery; it is not evidence that the patient has no account. The [error guide](https://docs.hi-doctor.ai/api-reference/errors.md) explains recovery without logging raw response bodies. ### How do lists work? REST does not have a universal cursor envelope. Consultations, categories and most collections return `count`, `next`, `previous`, `results` with `limit`/`offset` pagination. The default is 50 rows, maximum 200 on the default paginator. Journal and adverse-event lists have no documented maximum; request a small page. Orders and documents return arrays; pending subscription choices use a `pending` array. Follow `next` until `null`. Before attaching a bearer token, require the same HTTPS origin and expected collection path, reject credentials/fragments in the URL, and disable redirects. Keep filters and ordering unchanged; cap page count, detect repeated URLs and deduplicate records by ID because offset pages can shift. MCP list tools have their own schemas and concise fields (`total_count`, `has_more`, `next_offset`); do not assume the REST envelope applies. See [pagination](https://docs.hi-doctor.ai/api-reference/pagination.md) and [MCP workflows](https://docs.hi-doctor.ai/mcp/workflows.md). ### Frequently asked questions #### Can I test the API without a patient account? Only the public endpoints. The health check, questionnaire categories and questionnaire schemas need no token; everything that reads or changes patient data needs a verified patient account and its access token. #### How long does an access token last? An access token lasts one day and a refresh token 30 days. Refresh the access token as described in [Authentication](https://docs.hi-doctor.ai/api-reference/authentication.md). #### Why does sign-in return 401 for a new account? While the emailed code is still valid, password sign-in returns 401 with the verification required code; after the code expires it returns 401 with the invalid credentials code. Finish [sign-up and email verification](https://docs.hi-doctor.ai/api-reference/signup.md) first. #### Can I paste a token into an AI chat to test it? No. Never paste passwords, verification codes or tokens into an AI chat. An assistant should use the [MCP connector](https://docs.hi-doctor.ai/mcp/connect.md) instead. #### Does this quickstart change any data? No. Every example is a read. Submitting a questionnaire, paying and sending messages are covered in the guides linked under Next steps. ### Next steps - [Answer a questionnaire](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md): Ask one question at a time while Hi Doctor applies the branching and validation. - [Safe retries](https://docs.hi-doctor.ai/api-reference/idempotency.md): Retry a timed-out write without doing it twice, using Idempotency-Key. - [Uploads and downloads](https://docs.hi-doctor.ai/api-reference/uploads-downloads.md): Upload photos and documents, and save PDFs safely. - [API changelog](https://docs.hi-doctor.ai/api-reference/changelog.md): What changed in the patient REST API, and when. ## REST authentication: patient JWTs, refresh and Google Source: https://docs.hi-doctor.ai/api-reference/authentication.md Sign a Hi Doctor patient in over REST, send the JWT and brand header, refresh an expired access token and handle Google-only accounts. The Hi Doctor patient REST API authenticates with JWT access and refresh tokens that your application obtains when the patient signs in. An access token lasts one day and a refresh token 30 days, and every call also sends the `X-Brand-Slug: hi-doctor` header. Keep tokens on your server, out of logs and model prompts, and never ask a patient to paste a password, code or token into a chat. Last reviewed: 2026-11-04 The [MCP connector](https://docs.hi-doctor.ai/mcp/connect.md) uses its own account OAuth tokens. REST JWTs are not accepted at its `/mcp` endpoint, and clients must not use MCP tokens with REST. The separate [HTTP chat API](https://docs.hi-doctor.ai/mcp/http-chat.md) documents its own server-side authentication. ### How does a patient sign in? Follow [sign-up and email verification](https://docs.hi-doctor.ai/api-reference/signup.md) first. The patient must complete verification before password sign-in works. The [quickstart](https://docs.hi-doctor.ai/api-reference/quickstart.md) shows a read with securely supplied variables. For a verified password account, send `POST /v1/users/token/` with `Content-Type: application/json`, `X-Brand-Slug: hi-doctor` and an object containing `email` and `password`. Construct JSON with your client library, never by interpolating credentials into shell commands. Do not log the request or response. ```json Request body { "email": "patient@example.com", "password": "" } ``` ```json 200 { "access": "", "refresh": "" } ``` The success response also carries account metadata. Use the tokens; the metadata grants no extra access. ### What do sign-in failures look like? | Failure | Response shape | Recovery | | --- | --- | --- | | Missing required fields (`400`) | `email: ["This field is required."]` and `password: ["This field is required."]` | Fix request construction; credentials are not error messages | | Wrong credentials, or an expired verification code (`401`) | `error: "Invalid credentials"`, `code: "invalid_credentials"` | Give a neutral message; let the patient use supported sign-in or verification | | Registered but not yet verified (`401`) | `error: "Please verify your email"`, `code: "verification_required"` | Ask for the emailed code and call verify-email; do not retry sign-in | | Google-only account at password sign-in (`401`) | `error` and `code: "google_account"` | Offer Continue with Google on Hi Doctor; stop trying passwords | | Disabled account | `code: "user_inactive"` with `401` or `403`, depending on the endpoint | Stop and contact support | | Throttle (`429`) | `detail` and `Retry-After` | Wait; never loop sign-in attempts | Password sign-in for a Google-only account returns `401`; forgot-password returns `400` with the same code. Do not infer status from the code alone. See [errors and recovery](https://docs.hi-doctor.ai/api-reference/errors.md). ### Which headers does an authenticated call need? Send `X-Brand-Slug: hi-doctor` on every request, including sign-in and refresh. Unknown brands return `400` with `code: "unknown_brand"`. The header is not a credential; it does not authenticate a business or grant access to another patient's records. For authenticated REST calls, send `Authorization: Bearer ACCESS_TOKEN`. Missing credentials return `401` with `detail: "Authentication credentials were not provided."`. Records outside the account's access return `404`; never probe other identifiers. ### How do you refresh an expired access token? Send `POST /v1/users/token/refresh/` with `Content-Type: application/json`, the brand header and a JSON `refresh` field containing the securely stored refresh token. Success returns a new `access` value. ```json Request body { "refresh": "" } ``` REST refresh tokens are not rotated, and a password change or reset stops them from working. MCP refresh has its own rotation policy. On an explicit authentication failure, refresh once. A safe read can then be retried once; if refresh fails, return to sign-in. For a write, establish that it was refused before execution or reconcile its state first. A timeout, disconnected response or `5xx` does not prove failure and must never trigger automatic write replay. See [safe retries with Idempotency-Key](https://docs.hi-doctor.ai/api-reference/idempotency.md). ### Google sign-in Hi Doctor's Google sign-in obtains an ID token for its own Google OAuth client. `POST /v1/users/google-login/` accepts a `token` field and can create an account. A third-party app's Google token is not interchangeable, so this endpoint serves Hi Doctor's own apps. Assistants should use [MCP account authorisation](https://docs.hi-doctor.ai/mcp/connect.md), where sign-in happens on Hi Doctor and Google accounts work. ### Frequently asked questions #### How long do Hi Doctor REST tokens last? An access token lasts one day and a refresh token 30 days. Refresh the access token with the refresh token; when refresh fails, sign the patient in again. #### Are refresh tokens rotated? No. A REST refresh returns a new access token only. A password change or reset stops existing refresh tokens from working. #### Can I use a REST token with the MCP connector? No. The MCP connector uses its own OAuth tokens, issued when the patient signs in on hi-doctor.ai. REST and MCP tokens are not interchangeable. #### Why does password sign-in fail for some accounts? An account created with Google has no password, so password sign-in returns 401 with the Google account code. Offer Continue with Google on Hi Doctor instead. #### Can my app use its own Google sign-in? No. Google sign-in over REST accepts only ID tokens issued for Hi Doctor's own Google client, so it serves Hi Doctor's own apps. #### Does the brand header authenticate my application? No. The brand header selects the Hi Doctor brand and grants no access. Only the patient's access token authenticates a call. ### Next steps - [Quickstart](https://docs.hi-doctor.ai/api-reference/quickstart.md): Sign in and make your first authenticated read. - [Errors and recovery](https://docs.hi-doctor.ai/api-reference/errors.md): Error shapes, throttles and uncertain writes. - [MCP permissions](https://docs.hi-doctor.ai/mcp/permissions.md): How an AI assistant gets scoped access instead of a JWT. - [API changelog](https://docs.hi-doctor.ai/api-reference/changelog.md): Changes to authentication and the rest of the API. ## Hi Doctor REST API errors, throttles and recovery Source: https://docs.hi-doctor.ai/api-reference/errors.md Recognise observed REST error envelopes, handle cooldowns and throttles, reconcile uncertain writes and keep sensitive response data out of logs. Hi Doctor REST API errors do not share one envelope: depending on the endpoint you get a field map, a `detail` string, or an `error` with `code` or `error_name`, and unknown routes can return HTML or an empty body. Check the HTTP status and content type before parsing. This page shows the real shapes, how to wait on `429`, and how to recover from a write whose outcome is uncertain. Last reviewed: 2026-11-04 The examples below show real response shapes. They do not cover every failure. ### What do the error envelopes look like? #### Missing fields: HTTP 400 `POST /v1/users/token/` with an empty JSON object returns: ```json { "email": ["This field is required."], "password": ["This field is required."] } ``` These are error messages, not echoed email/password values. Validation can also use `non_field_errors` or nested field structures. An empty checkout request returned: ```json {"items": ["This field is required."]} ``` Fix the request; do not retry unchanged. Obtain clinical answers from the patient, never from a model's inference. #### Missing bearer: HTTP 401 `GET /v1/users/profile/me/` without a token returns: ```json {"detail": "Authentication credentials were not provided."} ``` The response carries `WWW-Authenticate: Bearer realm="api"`. A request after account deletion instead returns: ```json {"detail": "User not found", "code": "user_not_found"} ``` For access-token expiry, refresh once and retry a safe read once. If refresh fails, return to sign-in. Do not replay an uncertain write as part of token recovery. #### Application cooldown: HTTP 429 A repeated registration request returned this body, with `Retry-After: 59` as a header: ```json { "error": "Too many attempts. Please try again later.", "wait_seconds": 59, "retry_after_seconds": 59, "code": "registration_rate_limited", "error_name": "REGISTRATION_RATE_LIMITED" } ``` `wait_seconds` and `retry_after_seconds` are whole seconds and match the `Retry-After` header. Older clients saw `wait_seconds` as a string, so parse it defensively as a non-negative duration. The captured value is an example, not a fixed quota. #### Non-password accounts: endpoint-specific status Password sign-in returns an authentication failure (`401`) with the following body for a Google-only account: ```json { "error": "This account is registered with Google. Use 'Continue with Google' to sign in.", "code": "google_account" } ``` Forgot-password uses `400` with this code. Direct the patient to the supported Google sign-in flow; do not treat them as an unknown account or repeatedly try passwords. Wrong credentials use `invalid_credentials`; a registration whose emailed code is still valid uses `verification_required`; see [authentication](https://docs.hi-doctor.ai/api-reference/authentication.md). ### What should a client do for each status? | Status/outcome | Action | Automatic retry | | --- | --- | --- | | `400` field errors / `unknown_brand` | Correct the request or the brand header | No | | `401` | Restore authentication once; stop if refresh fails | Safe reads only after recovery | | `403` | Resolve permission/account restrictions | No | | `404` | Treat as absent or inaccessible; do not probe other IDs | No | | `406` | Check the endpoint's accepted response type; PDF routes may need `Accept: */*` | Only after fixing negotiation | | `409` | Read current state, then decide whether the action is still needed | No blind replay | | `410` | A stored file is unavailable; use support | No | | `415` | Use the endpoint's required media type, such as multipart for uploads | Only after correcting input | | `429` | Honour the available wait hint | Bounded safe reads; assess writes separately | | `5xx`, timeout or lost connection | A write may already have completed | Bounded safe reads only | | Clinical ineligibility (`400`, `code: "clinical_ineligibility"`) | Explain that this attempt is not eligible | Never change answers to bypass it | Clinical ineligibility is not a separate HTTP status. A guided questionnaire answer returns `400` with `code: "clinical_ineligibility"`, and a submission with ineligible answers returns `400` with a message saying the answers are not clinically eligible. Stop the attempted workflow and explain the outcome without offering medical advice. Every consultation is reviewed in full by an EU-licensed doctor, who decides on medical grounds only; see [Safety](https://docs.hi-doctor.ai/essentials/safety.md). ### How long should a client wait after a 429? Rate limits can return `Retry-After` with a `detail` message. Application cooldowns on the account endpoints return the same wait as `Retry-After`, `wait_seconds` and `retry_after_seconds`. Infrastructure errors can have none of them. No universal quota or remaining-capacity header is promised. Parse `Retry-After` as non-negative seconds or an HTTP date. Also inspect a valid `wait_seconds`; if both apply, wait at least the longer duration. Ignore malformed hints. For a safe read without a hint, use exponential backoff with jitter and a small attempt/time budget. For example, try at most three reads with delays around 1, 2 and 4 seconds; this is client policy, not a server quota. If the hint exceeds your time budget, stop and tell the user when to try again. Do not poll for instant clinical approval. Do not retry sign-in, verification codes or eligibility outcomes in a loop. ### How do you recover from an uncertain write? Questionnaire submission, messages, checkout, subscription changes, photo uploads and document uploads accept an `Idempotency-Key` header. Send one key per operation and resend it unchanged on every retry; the write then runs at most once, and a repeat returns the original status with `Idempotent-Replayed: true`. When the outcome is still unclear, `GET /v1/users/idempotency/{key}/` reports what happened without changing anything. See [safe retries with Idempotency-Key](https://docs.hi-doctor.ai/api-reference/idempotency.md) for the full list, the `409` and `422` codes and what a key cannot prove. The published specification declares the header on the operations listed there only; do not rely on deduplication anywhere else. JSON-RPC call IDs do not deduplicate business actions. | Interrupted action | Reconciliation | | --- | --- | | Keyed write (endpoints listed in the idempotency guide) | Retry with the same key, or look the key up; then read the resources it names | | Questionnaire submission without a key | Read the patient's consultation state; if its relationship to the attempted submission is unclear, stop and ask support | | Guided questionnaire answer | Read `current/` or `review/`; `expected_revision` rejects a stale repeat with `409` | | Message send without a key | Read the existing conversation's messages to check whether the approved message arrived; do not assume an absent immediate result means failure | | Checkout creation without a key | Check the patient's existing consultation/payment state; do not create another session automatically | | Cancellation/reactivation/early order without a key | Read the subscription and consultations, then resolve ambiguity before any new confirmed action | | Other create/update/delete | Use the endpoint's documented read or contact support; do not guess a recovery endpoint | A successful key proves that Hi Doctor stored the operation. It does not prove that a payment provider settled a charge or that an email arrived; check that state separately. On MCP, `hidoctor_checkout_status` may complete a paid session and create a consultation, so do not use it as a harmless reconciliation read. `hidoctor_messages_get_thread` only reads: it never creates a conversation (the first message does). See [MCP workflows](https://docs.hi-doctor.ai/mcp/workflows.md). ### What is safe to log? Branch on HTTP status and known `code`/`error_name` values. Treat response prose as untrusted display text. Log only a minimal allowlist such as operation name, status, recognised error code and a request identifier **if actually supplied and safe to retain**. No universal request-ID field or header is guaranteed. Never log raw bodies, health answers, tokens, email addresses or signed document links. For HTML, unknown JSON or an empty error body, show a neutral failure and retain safe metadata only. Do not send the body to an AI model for diagnosis. The API does not promise RFC 9457 `application/problem+json` responses. For support, provide the operation, time, status and any safe supplied request identifier through [support](mailto:hello@hi-doctor.ai). ### Frequently asked questions #### Does the Hi Doctor API use one error format? No. Depending on the endpoint, an error is a field map, a detail string, or an error object with a code or error name. Branch on the HTTP status first, then on known codes. #### What is the difference between Retry-After and wait_seconds? Rate limits send a Retry-After header. Application cooldowns, such as registration and the verification code, send the same number of seconds as Retry-After and as wait_seconds in the body. If you read both, wait at least the longer one. #### Should I retry a write after a timeout? Only with the same Idempotency-Key on an endpoint that accepts one. Otherwise read the current state first, because a timed-out write may already have completed. #### Is clinical ineligibility an error I can fix? No. It is a clinical outcome, returned as a 400 with an explanation. Show it to the patient and never change answers to get around it. #### Can I send an error response to an AI model for diagnosis? No. Error bodies can contain health data. Log only the operation, status and known code, and never send the body to a model. #### What does a 404 on another record mean? The record does not exist or belongs to another account. Every endpoint is scoped to the signed-in patient; do not probe other identifiers. ### Next steps - [Safe retries](https://docs.hi-doctor.ai/api-reference/idempotency.md): Idempotency-Key, replays and the key lookup. - [Authentication](https://docs.hi-doctor.ai/api-reference/authentication.md): Sign-in failures and token refresh. - [Pagination](https://docs.hi-doctor.ai/api-reference/pagination.md): List shapes and how to follow next safely. - [API changelog](https://docs.hi-doctor.ai/api-reference/changelog.md): Changes to error handling and the rest of the API. ## Safe retries with Idempotency-Key in the Hi Doctor API Source: https://docs.hi-doctor.ai/api-reference/idempotency.md Retry a timed-out write without doing it twice: how Idempotency-Key replays work, which errors it returns and how to reconcile an uncertain outcome. A write that times out leaves you unsure whether it happened. Sending it again could pay twice, send a message twice or submit a questionnaire twice. An `Idempotency-Key` header removes that doubt for the endpoints listed below: send the same key on every attempt, and Hi Doctor runs the operation at most once. Last reviewed: 2026-11-04 ### Which endpoints accept an Idempotency-Key? | Operation | Endpoint | | --- | --- | | Start a conversation | `POST /v1/users/conversations/` | | Send a message | `POST /v1/users/conversations/{id}/messages/` | | Create a checkout session | `POST /v1/users/checkout/` | | Submit a questionnaire | `POST /v1/users/questionnaires/{slug}/submit/` | | Confirm a renewal choice | `POST /v1/users/subscriptions/pending-choice/confirm/` | | Order a renewal early | `POST /v1/users/subscriptions/early-order/` | | Cancel or reactivate a subscription | `POST /v1/users/subscriptions/cancel/`, `POST /v1/users/subscriptions/reactivate/` | | Open the billing portal | `POST /v1/users/subscriptions/portal/` | | Upload or delete a consultation photo | `POST /v1/questionnaire/consultations/{id}/images/`, `DELETE .../images/{image_id}/` | | Upload or delete a document | `POST /v1/users/documents/`, `DELETE /v1/users/documents/{document_id}/` | The header is optional. Without it these endpoints behave exactly as before. The published specification declares the header on these operations only; do not rely on deduplication anywhere else. Guided questionnaire answers use [`expected_revision`](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#revisions-and-conflicts) instead. ### How do you use an Idempotency-Key? ### Create one key per operation Generate the key when the user asks for the action, for example a UUID, and store it with the pending operation. Keys are 8 to 128 ASCII letters, digits, underscores or hyphens. A malformed key returns `400` with `code: "invalid_idempotency_key"`. ### Send it with every attempt Put the same value in the `Idempotency-Key` header of the first request and of every retry. Keep the method, path, query and body identical. ### Read the outcome The first completed attempt returns the normal response. A repeat returns the same HTTP status with `Idempotent-Replayed: true` and a short body, described below. ```bash curl key="$(uuidgen)" # store it with the pending operation before the first attempt curl --silent --show-error --max-time 30 --request POST \ "$HD_BASE_URL/v1/users/conversations/$HD_CONVERSATION_ID/messages/" \ -H "Authorization: Bearer $HD_ACCESS_TOKEN" \ -H "X-Brand-Slug: hi-doctor" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $key" \ --data @message.json ``` ```python Python import os import uuid import requests base = os.environ["HD_BASE_URL"].rstrip("/") headers = { "Authorization": "Bearer " + os.environ["HD_ACCESS_TOKEN"], "X-Brand-Slug": "hi-doctor", "Accept": "application/json", "Idempotency-Key": str(uuid.uuid4()), # persist with the pending operation } url = base + "/v1/users/conversations/" + os.environ["HD_CONVERSATION_ID"] + "/messages/" body = {"content": "Message text written by the patient"} for attempt in range(3): try: response = requests.post(url, json=body, headers=headers, timeout=30) except requests.Timeout: continue # same key, same body: the message is sent at most once if response.status_code == 409 and response.json().get("code") == "idempotency_in_progress": continue # still running: wait, then retry with the same key break replayed = response.headers.get("Idempotent-Replayed") == "true" ``` ```javascript JavaScript import { randomUUID } from "node:crypto"; const base = process.env.HD_BASE_URL.replace(/\/$/, ""); const key = randomUUID(); // persist with the pending operation const url = `${base}/v1/users/conversations/${process.env.HD_CONVERSATION_ID}/messages/`; const body = JSON.stringify({ content: "Message text written by the patient" }); let response; for (let attempt = 0; attempt < 3; attempt++) { try { response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${process.env.HD_ACCESS_TOKEN}`, "X-Brand-Slug": "hi-doctor", Accept: "application/json", "Content-Type": "application/json", "Idempotency-Key": key, }, body, redirect: "error", signal: AbortSignal.timeout(30000), }); } catch { continue; // timeout or network error: retry with the same key } if (response.status !== 409) break; } const replayed = response?.headers.get("idempotent-replayed") === "true"; ``` Add a short, growing pause between attempts. See [errors and recovery](https://docs.hi-doctor.ai/api-reference/errors.md) for back-off guidance. ### What does a replay return? A replay does not resend the original body. It returns the original HTTP status with: ```json { "state": "succeeded", "resource_ids": {"message": ["6b0f8f9e-2d0c-4f4e-8d61-2f3c9a1b7e10"]}, "response_hash": "" } ``` `resource_ids` lists the records the original request created or changed. Read them through their normal endpoints to recover the full content, for example a message or a checkout session. `response_hash` lets you check a stored copy of the original response. A replayed `DELETE` returns `204` with no body; use the [lookup endpoint](#reconcile-an-uncertain-write) to see its result. If the first attempt failed with a `4xx` or `5xx`, that failure is stored too: retrying the same key returns the same status and no change is made. Fix the request and send it with a new key. ### Which errors can a key return? | Status | `code` | What to do | | --- | --- | --- | | `400` | `invalid_idempotency_key` | Use 8 to 128 letters, digits, `_` or `-` | | `409` | `idempotency_in_progress` | The first attempt is still running. Wait and retry with the same key | | `409` | `idempotency_key_expired` | The key is older than 30 days. Check the resources before creating a new operation | | `422` | `idempotency_key_reuse` | The key was already used for a different request. Use a new key only for a genuinely new operation | Never generate a new key to get around a `409`. That turns one operation into two. ### Reconcile an uncertain write When you cannot tell whether a keyed write completed, look the key up: ```bash curl --silent --show-error --max-time 15 \ "$HD_BASE_URL/v1/users/idempotency/$key/" \ -H "Authorization: Bearer $HD_ACCESS_TOKEN" \ -H "X-Brand-Slug: hi-doctor" \ -H "Accept: application/json" ``` ```json { "state": "succeeded", "method": "POST", "route": "users:create-checkout", "response_status": 201, "resource_ids": {"checkoutsession": ["3f1d2c4b-8a8e-4c1e-9a55-1f2e3d4c5b6a"]}, "created_at": "2026-10-07T09:15:02.481Z" } ``` The lookup is read-only. It never calls a payment provider and never changes anything. `state` is `in_progress`, `succeeded` or `failed`; a failed request rolled back its changes. A `404` means the key is unknown, belongs to another account or brand, is older than 30 days, or its request is still running. A request that has not finished is not visible yet, so a `404` straight after a timeout does **not** prove the write did not happen. Wait, look again, and if you are still unsure, retry the original request with the same key. The lookup endpoint is listed with the other [Account endpoints](https://docs.hi-doctor.ai/api-reference/tags/account.md). ### What does a key not cover? - **Effects outside Hi Doctor.** `succeeded` means Hi Doctor stored the operation and accepted any follow-up work. It does not prove that an email arrived, that a payment provider settled a charge or refund, or that a doctor signed a prescription. Check the relevant resource before you tell a patient that something is done. - **Upload storage.** A failed upload can leave an unreferenced file in storage. It is never attached to the patient's records. - **Two operations under one key.** Starting a conversation and sending its first message are two operations; give each its own key. - **Clinical decisions.** A key makes a retry safe. It does not change who decides: every consultation is reviewed in full by an EU-licensed doctor, who decides on medical grounds only. See [Safety](https://docs.hi-doctor.ai/essentials/safety.md). ### How long are keys kept? Keys are kept for 30 days per account and brand. After that the lookup returns `404` and a retry with the old key returns `409 idempotency_key_expired`, so an expired key is never silently run again. ### Frequently asked questions #### Do I have to send an Idempotency-Key? No. The header is optional, and requests without it behave as before. Send it on any write you might retry, such as checkout, a message or a questionnaire submission. #### What should I use as the key? Generate a random value, such as a UUID, when the user asks for the action, and store it with the pending operation. Keys are 8 to 128 letters, digits, underscores or hyphens. #### Can I reuse a key for a different request? No. A key used for a different request returns 422 with the key reuse code. Use a new key only for a genuinely new operation. #### What does a 409 in-progress response mean? The first attempt is still running. Wait, then retry with the same key; never create a new key to get around it. #### Does a successful key prove the payment or email went through? No. It proves Hi Doctor stored the operation. Check the payment, message or consultation itself before telling the patient that something is done. #### How long is a key remembered? Keys are kept for 30 days per account and brand. After that the lookup returns a not-found error and a retry with the old key is refused as expired. ### Next steps - [Errors and recovery](https://docs.hi-doctor.ai/api-reference/errors.md): Recover from uncertain writes, throttles and timeouts. - [Answer a questionnaire](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md): Guided answers use expected_revision instead of a key. - [Uploads and downloads](https://docs.hi-doctor.ai/api-reference/uploads-downloads.md): Upload photos and documents at most once. - [MCP approvals](https://docs.hi-doctor.ai/mcp/approvals.md): The connector sends its operation ID as the key on these endpoints. ## Answer a Hi Doctor questionnaire one question at a time Source: https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md 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? ### 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`. ### 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`. ### 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/`. ### Review When `complete` is `true`, `GET review/` shows every applicable answer with a readable `answer_label`. Check `ready_to_submit` and `missing_required_keys`. ### Submit Read `GET progress/` and post its `progress.data` to `POST submit/` as `{"data": [...]}`. The guided endpoints never submit on their own. 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 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 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 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 { "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 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 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 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 {"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 {"question_key": "mood-grid", "values_key": "interest", "value": {"interest": 0}} ``` - A follow-up has `option_input_key` (conditional fields use the form `__cf_`) and `parent_question_title`. Send the value in `option_inputs`: ```json {"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 { "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 {"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. `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 { "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 {"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 {"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.md), so a timed-out submission can be retried without creating a second one. Visiting the guided endpoints after a submission starts a fresh draft. 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 {"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. ## Hi Doctor MCP permissions and scopes Source: https://docs.hi-doctor.ai/mcp/permissions.md The seven permissions a Hi Doctor patient account can grant an AI app, the defaults, which actions can cost money, and what is never allowed. Hi Doctor MCP permissions are the seven OAuth scopes you approve when you connect an AI app to your patient account. A connected app can never do more than you approved, and never more than your own account can do on hi-doctor.ai. If the app asks for nothing specific, all seven are granted, including Payment, which is why actions with consequences still need your approval. Last reviewed: 2026-11-04 A permission never grants an ability on its own. It only allows a tool that the patient account can already use, and Hi Doctor's API checks every call again. Actions with consequences also need you to approve each one through an [approval link](https://docs.hi-doctor.ai/mcp/approvals.md). ### The seven permissions | Permission | Scope | What it allows | | --- | --- | --- | | **Your profile** | `hidoctor:profile` | Read your profile and prepare changes to it; follow an operation's approval state; export your own data; open guides and verify a prescription code. | | **Consultations, prescriptions, orders, billing and side-effect reports** | `hidoctor:consultations` | Read your consultations, photos, documents, prescriptions, orders and payments, prepare a reissue request, report a side effect, view, cancel or reactivate a plan, open the Stripe billing portal, see renewals and referrals. | | **Messages with your medical team** | `hidoctor:messages` | Read your conversation with the medical team and prepare messages and attachments. | | **Health questionnaires** | `hidoctor:questionnaires` | Complete a medical questionnaire and prepare its submission for a doctor to review. | | **Health tracking** | `hidoctor:progress` | View, add, edit and delete entries in the weight, injection and notes journal, and set the goal and injection plan. | | **Payment** | `hidoctor:checkout` | Prepare checkout and check its status, change a plan, confirm a renewal and order the next refill early. | | **Record changes** | `hidoctor:records:write` | Upload or delete your own documents and consultation photos, and prepare deletion of your own account. | ### Tools that need more than one permission Most tools need one permission, listed in the [patient reference](https://docs.hi-doctor.ai/mcp/tools/patient.md). These need more than one: | Tool | Needs | | --- | --- | | `hidoctor_documents_change`, `hidoctor_consultation_images_change` | `hidoctor:consultations` and `hidoctor:records:write` | | `hidoctor_account_delete` | `hidoctor:profile` and `hidoctor:records:write` | | `hidoctor_account_export` | `hidoctor:profile` and `hidoctor:consultations` | | `hidoctor_subscription_cancel`, `hidoctor_subscription_reactivate` | `hidoctor:consultations` and `hidoctor:checkout` | | `hidoctor_renewal_confirm`, `hidoctor_subscription_early_order` | `hidoctor:consultations`, `hidoctor:checkout` and `hidoctor:questionnaires` | If a tool has several actions, the actions you lack a permission for are left out of its schema. ### Defaults If the app does not ask for specific permissions, a patient account receives all seven: profile, consultations, messages, questionnaires, health tracking, payment and record changes. **Payment is included in the default.** The approval page shows it in its own box because it is the permission that can lead to a charge. An app can ask for fewer permissions. You cannot untick individual permissions on the approval page: to grant fewer, the app must request fewer. Scope values the server does not recognise, or that do not apply to the account, are dropped. ### Which permissions can cost money or cannot be undone? Preparing checkout is safe in itself: nothing is charged until you enter payment details on Stripe's page and confirm. These actions need more care: - **Ordering the next refill early** (Payment) creates the renewal consultation immediately, uses the one early order allowed in the paid period, and cannot be undone. - **Checking a checkout session** (Payment) can complete a paid session and create its consultation. - **Deleting your account** (Record changes) anonymises the account. Records that must legally be kept are retained. Ordering early and deleting your account wait for you to approve them through an [approval link](https://docs.hi-doctor.ai/mcp/approvals.md). Checking a checkout session is the one exception: it does not need an approval link, because it only checks a session you already started and paid on Stripe's page. Card details never reach the connector. Payment happens on Stripe's checkout, and card or plan changes happen in the Stripe billing portal. ### What is never allowed? No connected app can: - approve an action on a person's behalf: approval happens only on Hi Doctor, after that person signs in; - approve a consultation, issue a prescription or change a dose: those are medical decisions, explained on [Safety](https://docs.hi-doctor.ai/essentials/safety.md); - read a patient's data unless the signed-in account is that patient; - get past the clinical eligibility checks on a questionnaire; - see your password, which is entered only on hi-doctor.ai. ### Missing permissions Tools that need a permission you did not approve are left out of the app's tool list. If an app calls one anyway, Hi Doctor answers with an `insufficient_scope` challenge that names the permission needed. Apps that support this send you back to hi-doctor.ai to approve it, then repeat the request. In other apps, disconnect and connect again, approving the permission this time. ### What changes for existing connections A connection keeps exactly the permissions it was granted: a refresh can narrow the granted scopes but never widen them. Connections approved before 7 October 2026 therefore lack **Record changes**, and their tokens cannot prepare approval links: tools such as `hidoctor_message_send` return `reconnect_required`. Disconnect the app and connect it again to approve the current set once. ### Disconnecting Removing the connector in your AI app ends its access. How long access already issued lasts is explained on [Connect](https://docs.hi-doctor.ai/mcp/connect.md#disconnecting). ### Frequently asked questions #### What permissions does Hi Doctor give an AI app by default? All seven patient permissions: profile, consultations, messages, questionnaires, health tracking, payment and record changes. An app can request fewer. #### Can I untick a permission on the approval page? No. To grant fewer permissions, the app must request fewer. The approval page shows Payment and Health tracking in their own boxes so you can see them clearly. #### Can a connected app charge my card? No. Card details never reach the connector. A charge only happens when you complete Stripe's checkout yourself, and ordering a refill early waits for your approval. #### Can an AI app change my prescription or dose? No. Those are medical decisions made by an EU-licensed doctor, and no permission or tool allows them. #### What happens if an app calls a tool I did not approve? Hi Doctor answers with an insufficient_scope challenge naming the permission. The app can send you back to approve it, or you can disconnect and connect again. #### Why does an older connection return reconnect_required? Connections approved before 7 October 2026 cannot prepare approval links. Disconnect the app and connect it again. ### Next steps - [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md): how actions with consequences wait for your approval. - [Security](https://docs.hi-doctor.ai/mcp/security.md): how sign-in, tokens and permissions are enforced. Ready to see a doctor? [Start a consultation on hi-doctor.ai](https://hi-doctor.ai/consultation). ## Hi Doctor MCP workflows: patient tasks step by step Source: https://docs.hi-doctor.ai/mcp/workflows.md Step-by-step Hi Doctor MCP tasks: questionnaires, checkout, prescriptions, messages, progress journal, plans, side effects and long lists. This page walks through the patient tasks people most often do with the Hi Doctor MCP connector, step by step. Each task names the tools involved and the points where the assistant must stop and ask the patient. Look up any tool in the [patient tools reference](https://docs.hi-doctor.ai/mcp/tools/patient.md), and read [approval links](https://docs.hi-doctor.ai/mcp/approvals.md) before building a task that changes data. Last reviewed: 2026-11-04 ### How do approval links fit into a task? Several steps below use an approval link. The tool prepares the action, the patient approves it on Hi Doctor, and the assistant commits it with the same arguments, the `confirmation_id` and an `idempotency_key`. Some actions can be prepared but are completed on hi-doctor.ai instead; the [patient reference](https://docs.hi-doctor.ai/mcp/tools/patient.md) marks them **Available on the website**. The states and safe retries are on [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md). ### Complete a consultation questionnaire The server decides which question comes next, which branches apply and when the answers are complete. The assistant asks, records the patient's own answers and never answers for them. ### Pick the category `hidoctor_questionnaire_categories` lists the categories and their slugs, for example `weight-loss` or `hair-growth`. Pass the slug as `category_slug` on every call. ### Start or resume `hidoctor_questionnaire_start` returns the first unanswered question with its options and progress, and resumes a questionnaire that was already started. `hidoctor_questionnaire_current` returns the current question later without changing anything. ### Ask and save each answer Ask the question as returned, in the patient's language, and offer its options. Send the patient's own answer with `hidoctor_questionnaire_answer`; it returns the next question. Repeat until the result says the questionnaire is complete. ### Go back when something needs changing `hidoctor_questionnaire_back` returns to an earlier question, grid item or follow-up without changing any answer. Send the corrected answer with `hidoctor_questionnaire_answer`. If a change closes a branch, the answers that no longer apply are dropped and listed in the result. ### Review with the patient `hidoctor_questionnaire_review` returns every answer as readable text, plus any required item still missing. Read it back and ask the patient to confirm. ### Submit through an approval link `hidoctor_questionnaire_submit` prepares the submission and returns an `approval_url`. The patient approves it on Hi Doctor. Poll `hidoctor_operation_get`, then commit with the same arguments, `confirmation_id` and an `idempotency_key`. A committed submission has one of three outcomes: `submitted` (sent to a doctor for review), `ineligible` (a clinical result; never re-answer to change it) or `covered_by_active_plan` (the patient's plan already covers it). #### Which answer formats does the questionnaire accept? The MCP tools accept the same answer bodies as the REST questionnaire, so the shapes are documented once, in the [questionnaire guide](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md): | What you need | Where it is described | | --- | --- | | A single, multiple-choice, input or grid answer | [Send an answer](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#send-an-answer) | | Skip an optional question | [Skip an optional question](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#skip-an-optional-question) | | One grid row (`values_key`) or follow-up (`option_input_key`) | [Answer one grid item or follow-up](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#answer-one-grid-item-or-follow-up) | | Several answers at once in `answers` | [Send a batch](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#send-a-batch) | | `expected_revision` and stale drafts | [Revisions and conflicts](https://docs.hi-doctor.ai/api-reference/questionnaire-guide.md#revisions-and-conflicts) | Two points are specific to the connector: - **Optional revision check.** `expected_revision` is optional in `hidoctor_questionnaire_answer`. Without it, the tool uses the revision it has just read. Pass it from the previous result when the patient may also be editing on hi-doctor.ai. - **Older categories.** Skips, batches, `expected_revision` and single grid or follow-up answers need the guided questionnaire. A category that does not offer it returns `guided_api_required`; answer one whole question at a time instead. The assistant rules for questionnaires are on [Build on Hi Doctor with AI](https://docs.hi-doctor.ai/mcp/ai-integration.md). ### Pay for a consultation Checkout is completed on hi-doctor.ai, not through the connector. `hidoctor_checkout_create` can prepare a checkout and show it for approval, but its commit returns `backend_atomicity_required`. Tell the patient this before they approve anything. ### Confirm what the patient is requesting Agree the treatment, dose and plan (`one_off` or `subscription`) the patient wants to request. The doctor decides whether it is appropriate. ### Send the patient to hi-doctor.ai The patient signs in on hi-doctor.ai, reviews the treatment, plan and amount, and pays on Stripe's page. Card details are never entered in chat. ### Optionally show the checkout first If the patient wants to see the amount in the conversation, `hidoctor_checkout_create` with `treatment_id`, `dosage` and `plan` prepares it and returns an `approval_url`, or lists missing profile fields. Committing it returns `backend_atomicity_required`, and nothing is charged. ### Confirm the payment After the patient pays, `hidoctor_consultations_list` shows the new consultation. If you hold a checkout `session_id`, `hidoctor_checkout_status` reports whether it was paid. It can finalise a paid session and create the consultation, so call it once, not in a loop. Do not tell the patient that a checkout is paid or complete because a tool returned an `approval_url`. Payment happens only on hi-doctor.ai, through Stripe. See [Actions that cannot complete yet](https://docs.hi-doctor.ai/mcp/approvals.md#actions-that-cannot-complete-yet). ### Get a prescription or invoice 1. `hidoctor_prescriptions_list` or `hidoctor_payments_list` to find the right record. 2. `hidoctor_prescription_pdf` or `hidoctor_payment_invoice` for the document. The result is a link and metadata, never the PDF bytes. Its `delivery` field says how the link opens; a `signed_redirect` link opens without signing in. 3. Treat the document as confidential: give it only to the patient, never repeat it in a shared place, and do not store it. For a refund, `hidoctor_payment_credit_note` returns the credit note the same way. If a prescription expired or was lost, `hidoctor_prescription_request_reissue` prepares a request to the medical team; the request itself is completed on hi-doctor.ai. How prescriptions reach a pharmacy is explained on [How it works](https://docs.hi-doctor.ai/essentials/how-it-works.md). ### Message the medical team ### Check for replies `hidoctor_messages_unread_summary` shows whether there are unread replies. `hidoctor_messages_get_thread` reads the conversation without creating it or marking it read. ### Open the conversation if needed If the patient has no conversation yet, `hidoctor_conversation_open` creates it, or reuses the existing one. ### Prepare the exact text Agree the wording with the patient, then call `hidoctor_message_send` with `content`. It returns an `approval_url`. ### Approve and commit The patient approves the exact text on Hi Doctor. Poll `hidoctor_operation_get`, then commit with the same `content`, `confirmation_id` and an `idempotency_key`. After a timeout, repeat the commit with the same key; never prepare the message again. ### Mark as read `hidoctor_messages_mark_read` marks the conversation as read once the patient has seen the replies. #### How do I send a file to the medical team? Upload the file first through the connector's file upload, which returns a `file_ref`: a short reference to the uploaded file that tools accept instead of the file's bytes. Then prepare `hidoctor_message_attachment_send` with that `file_ref` and an optional caption, and follow the same approval steps. `hidoctor_message_attachment_get` returns an attachment as a link. File uploads need an MCP client that supports them. ### Keep the progress journal Start with `hidoctor_progress_status`: it says whether the journal is enabled, needs an active subscription, or is unavailable. The journal is offered with weight-loss treatment. | Entry | How it behaves | | --- | --- | | Weight | One entry per day. `hidoctor_progress_weight_log` replaces the entry for that date, using today in the profile's time zone if no date is given. | | Injections | Each call to `hidoctor_progress_injection_log` creates a new entry. The time needs a time-zone offset. | | Notes | Each call to `hidoctor_progress_note_log` creates a new entry, even on the same day. | | Injection plan | `hidoctor_progress_plan_set` takes the preferred time as `HH:MM`. The older values `morning`, `afternoon` and `evening` are stored as 09:00, 14:00 and 19:00. | To correct an entry, call `hidoctor_progress_day_get` for the day; it returns each entry's ID for the update tools. Deleting an entry is completed on hi-doctor.ai. The older names `hidoctor_progress_injections_log` and `hidoctor_progress_notes_add` still run for now but are hidden from the tool list; update saved prompts to the new names (see [Patient tools](https://docs.hi-doctor.ai/mcp/tools/patient.md)). ### Manage a subscription - `hidoctor_subscription_get` shows each plan and the end of its current period. - `hidoctor_subscription_cancel` prepares cancellation of one plan, by `subscription_id`, for the **end of the paid period**, never immediately. `hidoctor_subscription_reactivate` undoes it before that date. Both use an approval link. - `hidoctor_subscription_billing_portal` returns a confidential Stripe link for card, plan and billing details. - `hidoctor_subscription_early_order` brings the next refill forward. It creates the renewal consultation now, is allowed once per paid period and **cannot be undone**. Explain that and get an explicit yes before preparing it; the patient then approves it through the approval link. - When a renewal consultation is waiting for the patient's choice, `hidoctor_renewal_get` with `action: "pending"` shows it, and `hidoctor_renewal_confirm` prepares the patient's choice for approval. Fees and refunds are explained on [Pricing](https://docs.hi-doctor.ai/essentials/pricing.md). ### Report a side effect `hidoctor_adverse_event_report` sends a suspected side effect to the clinical team for review. Ask for each detail (medicine, what happened, severity, when it started) instead of guessing. It is not an emergency channel: for severe or sudden symptoms, tell the patient to contact their local emergency number. See [Safety](https://docs.hi-doctor.ai/essentials/safety.md). ### Lists and pagination List tools take `limit` and `offset` and a `detail` mode: | Mode | What it returns | | --- | --- | | `detail: "concise"` (default) | A short summary of each row, up to 50 rows, with `total_count`, `has_more` and `next_offset`. | | `detail: "full"` | The page as the Hi Doctor API sends it, with every field and the API's own `count`. | When `has_more` is true, call the same tool again with `offset` set to `next_offset`, keeping the same filters and ordering, until `has_more` is false. With `detail: "full"`, keep raising `offset` by `limit` until `count` is reached. To count or add up rows, for example "how much have I paid in total", page to the end before you add anything. Results are also capped in size, in both modes. If a page is too large to return whole, the server returns fewer rows with `truncated: true`, `has_more` and a `next_offset`; it never cuts a row in half. A single record that does not fit returns a `result_too_large` error: ask for a smaller `limit`. ### Frequently asked questions #### Can an assistant answer the questionnaire for the patient? No. The assistant asks each question as returned and records only what the patient said. Hi Doctor's servers cannot tell whether an answer came from the patient, so the assistant must never pick, infer or suggest an answer. #### What does an ineligible questionnaire result mean? It is a clinical result, not an error. Explain it plainly and do not send different answers to change it. The patient can message the medical team with questions. #### Can a patient pay for a consultation through the connector? No. Checkout is completed on hi-doctor.ai, and payment happens on Stripe's page. The connector can show a prepared checkout for approval, but its commit returns backend_atomicity_required and nothing is charged. #### Why is a prescription or invoice not attached to the result? Document tools return a link and metadata, never the PDF itself. Treat the link as confidential, give it only to the patient and do not store it. #### What happens if a commit times out? Repeat the same commit with the same confirmation ID, arguments and idempotency key. Hi Doctor checks what happened and never runs the action twice. Never prepare the action again to escape a pending state. #### Why does logging a weight replace the earlier entry? The journal keeps one weight per day, so logging a weight for a date replaces that day's entry. Injections and notes work differently: each call adds a new entry. #### How do I get every row of a long list? Call the same list tool again with offset set to next_offset until has_more is false. Page to the end before counting or adding anything up. ### Next steps - [Build on Hi Doctor with AI](https://docs.hi-doctor.ai/mcp/ai-integration.md): the system prompt and rules an assistant must follow. - [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md): operation states, expiry and safe retries. - [Patient tools reference](https://docs.hi-doctor.ai/mcp/tools/patient.md): every tool, its permission and its hints. - [Troubleshooting](https://docs.hi-doctor.ai/mcp/troubleshooting.md): fixes for sign-in, permission and tool errors. Ready to see a doctor? [Start a consultation on hi-doctor.ai](https://hi-doctor.ai/consultation). ## Streamable HTTP and OAuth for any MCP client Source: https://docs.hi-doctor.ai/mcp/streamable-http.md Connect any MCP client or your own code to Hi Doctor: endpoint, protocol versions, OAuth discovery, PKCE, token lifetimes, limits and errors. Hi Doctor is a remote MCP server at `https://mcp.hi-doctor.ai/mcp` that uses the Streamable HTTP transport with OAuth 2.1 authorization. Use this page when your client has no dedicated guide or when you write your own MCP client. It is also the reference for token lifetimes, rate limits and result size limits. Last reviewed: 2026-11-04 ### Endpoint | | | | --- | --- | | MCP endpoint | `POST https://mcp.hi-doctor.ai/mcp` | | Transport | Streamable HTTP, stateless: each POST is handled on its own and no `Mcp-Session-Id` is issued | | Requests | JSON-RPC 2.0 in the POST body, `Content-Type: application/json` | | Accept | Send `Accept: application/json, text/event-stream`; read the reply either as JSON or as a single-request SSE stream | | Authorization | `Authorization: Bearer ` issued by Hi Doctor's authorization server | | Not supported | `GET` and `DELETE` on `/mcp` and `/` (HTTP 405), the legacy HTTP+SSE transport (`/sse`), stdio, and client ID metadata documents | `POST https://mcp.hi-doctor.ai` (no path) is an alias of the same resource: tokens bound to `/mcp` work there too. `/mcp` is the canonical resource; use it in configuration and as the OAuth `resource` value, never the no-path form. `GET https://mcp.hi-doctor.ai/healthz` needs no token and returns the service status. ### Protocol versions The server uses the legacy `initialize` handshake and negotiates these protocol versions: `2025-11-25` (latest), `2025-06-18`, `2025-03-26`, `2024-11-05` and `2024-10-07`. The [2026-07-28 specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports) removes `initialize` and protocol sessions and adds `server/discover`. Hi Doctor does not support the 2026-07-28 protocol; clients that support both should fall back to a 2025 version. Any change is announced in the [changelog](https://docs.hi-doctor.ai/mcp/changelog.md). ### Authorization Hi Doctor's MCP server is both the OAuth authorization server and the protected resource. Sign-in itself is delegated to hi-doctor.ai, as described on [Connect](https://docs.hi-doctor.ai/mcp/connect.md). | Step | Detail | | --- | --- | | Challenge | A request without a valid token gets HTTP 401 with `WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://mcp.hi-doctor.ai/.well-known/oauth-protected-resource/mcp", scope="…"`. `scope` lists every scope the server supports. Request it as given: Hi Doctor grants the ones a patient account can hold (the seven below) and drops the rest | | Step-up | A valid token without the permission a tool needs gets HTTP 403 with `WWW-Authenticate: Bearer error="insufficient_scope", scope="…", resource_metadata="…"`. `scope` lists the permissions to request: those already granted plus the missing one | | Protected resource metadata (RFC 9728) | `https://mcp.hi-doctor.ai/.well-known/oauth-protected-resource/mcp` (also served without the `/mcp` suffix) | | Authorization server metadata (RFC 8414) | `https://mcp.hi-doctor.ai/.well-known/oauth-authorization-server` | | Client registration | Dynamic client registration (RFC 7591) at `POST /register`, public clients only (`token_endpoint_auth_method: none`). Client ID metadata documents are not supported | | Authorization | `GET /authorize` with `response_type=code`, PKCE `code_challenge_method=S256` (required; `plain` is rejected), `state` and `resource=https://mcp.hi-doctor.ai/mcp` | | Authorization response | The redirect carries `code` and `state`, plus `iss=https://mcp.hi-doctor.ai` (RFC 9207). Error redirects carry `iss` too. Check that it matches the issuer in the authorization server metadata | | Redirect URIs | Must exactly match a URI registered for the client. Loopback redirects may use any port (RFC 8252) | | Token | `POST /token` with `authorization_code` or `refresh_token` grants | | Revocation (RFC 7009) | `POST /revoke` with `token=`. Revokes the whole connection: every token from it is rejected on the next request. Answers HTTP 200 even for an unknown or expired token | | Scopes a patient account can hold | `hidoctor:profile`, `hidoctor:consultations`, `hidoctor:messages`, `hidoctor:questionnaires`, `hidoctor:checkout`, `hidoctor:progress`, `hidoctor:records:write`. The metadata's `scopes_supported` and the 401 challenge list more; scopes that do not apply to a patient account are dropped at grant time | #### Token lifetimes and rotation - Authorization codes are single-use and expire after 60 seconds. - Access tokens last at most one hour. - Refresh tokens last 30 days and are single-use: each refresh returns a new one, and reusing an old one fails with `invalid_grant`. - Each token is bound to the `/mcp` resource and rejected anywhere else. - A revoked connection's tokens are rejected at once, whatever their expiry. If you request no scopes, the patient defaults apply. Requested scopes that do not apply to the account are dropped. See [Permissions](https://docs.hi-doctor.ai/mcp/permissions.md#defaults). Only tokens issued by this authorization server are accepted. A Hi Doctor REST API token (from `/v1/users/token/`) is not an MCP token and must never be sent to the MCP endpoint, and an MCP token is never forwarded to the REST API by your client. Call the revocation endpoint when the user removes your client's connection, so access ends at once instead of when the access token expires. #### Step-up for a missing permission When a client calls a tool whose permission the user did not approve, the server answers with HTTP 403 before the tool runs. Start a new authorization with the `scope` value from the challenge, let the user approve it, and repeat the call with the new token. A client that does not support step-up should tell the user to disconnect and connect again. ### A minimal session Every request is a separate POST with the bearer token. Replace `$HD_MCP_TOKEN` with an access token your client obtained through OAuth; do not paste tokens into shared logs. ### Initialize ```bash curl -sS https://mcp.hi-doctor.ai/mcp \ -H "Authorization: Bearer $HD_MCP_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0.0"}}}' ``` The result includes the negotiated `protocolVersion`, the server's capabilities and short `instructions` for the account. Send `MCP-Protocol-Version` with the negotiated version on later requests. ### List tools ```bash curl -sS https://mcp.hi-doctor.ai/mcp \ -H "Authorization: Bearer $HD_MCP_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' ``` You receive only the tools for the account and its approved scopes. ### Call a read-only tool ```bash curl -sS https://mcp.hi-doctor.ai/mcp \ -H "Authorization: Bearer $HD_MCP_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"hidoctor_profile_get","arguments":{}}}' ``` Results carry `structuredContent` that matches the tool's output schema, plus the same data as JSON text for clients that only read text. A result is capped at about 20,000 tokens; larger lists come back shorter with `has_more` and `next_offset`. See [Lists and pagination](https://docs.hi-doctor.ai/mcp/workflows.md#lists-and-pagination). ### Limits | Limit | Value | | --- | --- | | MCP requests per access token | 120 a minute (fixed window). Over the limit: HTTP 429 with `Retry-After` | | Token requests per address | 30 a minute. Over the limit: HTTP 429 `{"error":"slow_down"}` with `Retry-After` | | Request body | 1 MB | | Tool result size | About 20,000 tokens. Lists come back shorter with `has_more` and `next_offset` | | Approval link | Valid for 15 minutes; receipts readable for 30 days. See [Approval links](https://docs.hi-doctor.ai/mcp/approvals.md) | ### Errors | Situation | Response | | --- | --- | | Missing, expired or invalid token | HTTP 401 with the `WWW-Authenticate` challenge above. Refresh, or sign in again | | More than 120 requests a minute with one token | HTTP 429 with `Retry-After`. Wait before retrying | | More than 30 token requests a minute from one address | HTTP 429 `{"error":"slow_down"}` with `Retry-After` | | `GET` or `DELETE` on `/mcp` or `/` | HTTP 405 | | Unknown tool | JSON-RPC error `-32602` (invalid params) | | A tool that is not available to the account | HTTP 403 `{"error":"forbidden"}`. Consent cannot add it | | Tool whose permission was not approved | HTTP 403 `insufficient_scope` step-up challenge, described above | | Result too large to return, even in part | Tool result with `isError: true` and code `result_too_large`. Ask for fewer rows or a shorter date window | | Validation, not found, conflict or upstream failure | Tool result with `isError: true` and `structuredContent.error` containing `code`, `message` and, where useful, `field`, `hint` or `missing_fields` | Tool errors are designed for the model to read and recover from. Retry reads after a pause; do not repeat a write until you have read the current state. ### Related reading - [Security](https://docs.hi-doctor.ai/mcp/security.md): how tokens and permissions are enforced. - [Troubleshooting](https://docs.hi-doctor.ai/mcp/troubleshooting.md): sign-in and connection problems. - [MCP transports](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) and [authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) in the specification. ### Frequently asked questions #### Which MCP transport does Hi Doctor use? Streamable HTTP, stateless. Each POST to https://mcp.hi-doctor.ai/mcp is handled on its own, and the legacy HTTP+SSE transport and stdio are not supported. #### Which MCP protocol versions does Hi Doctor support? 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 and 2024-10-07, negotiated through the initialize handshake. The 2026-07-28 protocol is not supported. #### How long do Hi Doctor MCP tokens last? Access tokens last at most one hour. Refresh tokens last 30 days and are single-use, and authorization codes expire after 60 seconds. #### Can I revoke a Hi Doctor MCP token? Yes. POST the token to /revoke. The whole connection is revoked and every token from it is rejected on the next request. #### Can I send a Hi Doctor REST API token to the MCP endpoint? No. Only tokens issued by the MCP authorization server are accepted, and an MCP token must never be sent to the REST API. #### What happens when a tool needs a permission the user did not approve? The server answers HTTP 403 with an insufficient_scope challenge. Start a new authorization with the scope it names, then repeat the call. #### What is the rate limit? 120 MCP requests a minute per access token and 30 token requests a minute per address, each counted in a fixed one-minute window. Over the limit, wait for the Retry-After value. ### Next steps - [Agent quickstart](https://docs.hi-doctor.ai/ai/quickstart.md): a complete agent flow from connection to first tool call. - [Security](https://docs.hi-doctor.ai/mcp/security.md): how tokens and permissions are enforced. agent-resources-sha256: bf3d60c680eddc736f2cecea06f22c74928da119cb9e5a11eb2396a8b66c645a