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

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

# Get a questionnaire

> The published questionnaire for a product. Answer it as a list of entries keyed by question_key and send schema_version and schema_hash back with the consultation. Without locale the labels carry message keys only (each localizable field keeps its <field>Key either way). With locale the labels are written in that language, falling back to English (en) for an unsupported locale and per missing message, and no display field (a label, title or error text) carries a message key: text that no catalogue has is null, and its <field>Key still names it. The response's locale says which one was used and schema_hash is the hash of that localized schema. A submission accepts the current version's hash with or without a locale and in any supported locale. Needs no scope.

### Access, effects and retries
**Ownership:** The API key's own partner and mode (test or live); another partner's or mode's records answer 404.

**Prerequisites:** A partner API key; no scope is needed.

**Effects:** None.

**Retries:** Safe to retry.



## OpenAPI

````yaml /api-reference/partner/openapi.json get /v1/partner/questionnaires/{slug}/
openapi: 3.0.3
info:
  title: Hi Doctor Partner API
  version: '1.0'
  description: >-
    The Hi Doctor Partner API lets a partner submit online medical consultations
    for its patients, follow their clinical review, answer the doctor's
    information requests and read the resulting prescriptions. Partner requests
    use Authorization: Bearer <api key> with a partner API key; test keys and
    live keys are separate. See https://docs.hi-doctor.ai/partners/overview for
    the partner guides.
  contact:
    name: Hi Doctor
    email: hello@hi-doctor.ai
    url: https://docs.hi-doctor.ai
servers:
  - url: https://api.hi-doctor.ai
    description: Production
security: []
tags:
  - name: Connectivity
    description: Check that a key works.
  - name: Products
    description: What you can offer, where, and at what price.
  - name: Questionnaires
    description: The questions a patient answers for a product.
  - name: Files
    description: Photos and documents attached to answers.
  - name: Consultations
    description: Submit consultations and follow their review.
  - name: Patients
    description: The patients you submitted consultations for, and their consent.
  - name: Information requests
    description: Questions the doctor sends back during review.
  - name: Prescriptions
    description: Prescriptions issued after an approval.
  - name: Webhooks
    description: Receive events instead of polling.
  - name: Test mode
    description: Tools that only work with test keys.
paths:
  /v1/partner/questionnaires/{slug}/:
    get:
      tags:
        - Questionnaires
      summary: Get a questionnaire
      description: >-
        The published questionnaire for a product. Answer it as a list of
        entries keyed by question_key and send schema_version and schema_hash
        back with the consultation. Without locale the labels carry message keys
        only (each localizable field keeps its <field>Key either way). With
        locale the labels are written in that language, falling back to English
        (en) for an unsupported locale and per missing message, and no display
        field (a label, title or error text) carries a message key: text that no
        catalogue has is null, and its <field>Key still names it. The response's
        locale says which one was used and schema_hash is the hash of that
        localized schema. A submission accepts the current version's hash with
        or without a locale and in any supported locale. Needs no scope.


        ### Access, effects and retries

        **Ownership:** The API key's own partner and mode (test or live);
        another partner's or mode's records answer 404.


        **Prerequisites:** A partner API key; no scope is needed.


        **Effects:** None.


        **Retries:** Safe to retry.
      operationId: get_questionnaire
      parameters:
        - in: path
          name: slug
          schema:
            type: string
          description: The questionnaire slug.
          required: true
          example: hair-growth
        - in: query
          name: product_id
          schema:
            type: string
          description: Pick the questionnaire variant for this product.
          example: oral_finasteride
        - in: query
          name: locale
          schema:
            type: string
          description: >-
            Language for the labels: one of en, es, zh. Any other value falls
            back to English.
          example: es
      responses:
        '200':
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: >-
                Requests allowed in the current window for the most restrictive
                limit that applies. Absent on refusals made before the throttles
                run (brand header, flag off, partner_not_live).
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix time, in seconds, when the window frees up.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Questionnaire'
              examples:
                example-1:
                  value:
                    slug: hair-growth
                    schema_version: '412'
                    schema_hash: <HASH>
                    locale: null
                    answer_format: entries-v1
                    upload_answer:
                      value: <upload_id>
                    schema:
                      steps:
                        - key: hair_loss_duration
                          kind: single
                          label: categories.hairGrowth.form.steps.duration.label
                          labelKey: categories.hairGrowth.form.steps.duration.label
                          required: true
                          options:
                            - value: under_1_year
                              label: >-
                                categories.hairGrowth.form.steps.duration.options.under1Year
                              labelKey: >-
                                categories.hairGrowth.form.steps.duration.options.under1Year
                            - value: over_1_year
                              label: >-
                                categories.hairGrowth.form.steps.duration.options.over1Year
                              labelKey: >-
                                categories.hairGrowth.form.steps.duration.options.over1Year
                  summary: Illustrative response example
                example-2:
                  value:
                    slug: hair-growth
                    schema_version: '412'
                    schema_hash: <HASH>
                    locale: es
                    answer_format: entries-v1
                    upload_answer:
                      value: <upload_id>
                    schema:
                      steps:
                        - key: hair_loss_duration
                          kind: single
                          label: ¿Desde cuándo notas la caída del pelo?
                          labelKey: categories.hairGrowth.form.steps.duration.label
                          required: true
                          options:
                            - value: under_1_year
                              label: Menos de un año
                              labelKey: >-
                                categories.hairGrowth.form.steps.duration.options.under1Year
                            - value: over_1_year
                              label: Más de un año
                              labelKey: >-
                                categories.hairGrowth.form.steps.duration.options.over1Year
                  summary: Illustrative response example
          description: Success.
        '400':
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: >-
                Requests allowed in the current window for the most restrictive
                limit that applies. Absent on refusals made before the throttles
                run (brand header, flag off, partner_not_live).
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix time, in seconds, when the window frees up.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerError'
              examples:
                example-1:
                  value:
                    error:
                      type: invalid_request
                      code: brand_header_not_allowed
                      message: >-
                        Brand headers are not accepted on partner requests; the
                        brand comes from the API key.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
          description: >-
            The request is malformed. Partner routes also refuse the patient-app
            brand header with 400 brand_header_not_allowed, before
            authentication (no rate-limit headers).
        '401':
          headers:
            WWW-Authenticate:
              schema:
                type: string
              description: >-
                The authentication scheme to use: Bearer
                realm="hi-doctor-partner".
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerError'
              examples:
                example-1:
                  value:
                    error:
                      type: authentication_error
                      code: not_authenticated
                      message: Authentication credentials were not provided.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
                example-2:
                  value:
                    error:
                      type: authentication_error
                      code: invalid_token
                      message: The API key is not valid.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
                example-3:
                  value:
                    error:
                      type: authentication_error
                      code: key_revoked
                      message: The API key has been revoked.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
                example-4:
                  value:
                    error:
                      type: authentication_error
                      code: key_expired
                      message: The API key has expired.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
                example-5:
                  value:
                    error:
                      type: authentication_error
                      code: partner_suspended
                      message: The partner account is suspended.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
          description: >-
            The API key is missing, malformed, unknown, revoked or expired, or
            the partner account is suspended. The response carries
            WWW-Authenticate: Bearer realm="hi-doctor-partner" and no rate-limit
            headers.
        '403':
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: >-
                Requests allowed in the current window for the most restrictive
                limit that applies. Absent on refusals made before the throttles
                run (brand header, flag off, partner_not_live).
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix time, in seconds, when the window frees up.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerError'
              examples:
                example-1:
                  value:
                    error:
                      type: permission_error
                      code: partner_not_live
                      message: Live mode is not enabled for this partner.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
          description: >-
            A live key was used while live mode is not enabled for the partner.
            This operation needs no scope.
        '404':
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: >-
                Requests allowed in the current window for the most restrictive
                limit that applies. Absent on refusals made before the throttles
                run (brand header, flag off, partner_not_live).
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix time, in seconds, when the window frees up.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/PartnerError'
                  - $ref: '#/components/schemas/PartnerFlagOffError'
              examples:
                example-1:
                  value:
                    error:
                      type: not_found
                      code: not_found
                      message: No such resource.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
                example-2:
                  value:
                    error:
                      type: not_found
                  summary: Illustrative response example
          description: >-
            No such resource for this key's partner and mode. While the Partner
            API is switched off for the environment, every route answers 404
            with only {"error": {"type": "not_found"}}, before authentication.
        '429':
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: >-
                Requests allowed in the current window for the most restrictive
                limit that applies. Absent on refusals made before the throttles
                run (brand header, flag off, partner_not_live).
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix time, in seconds, when the window frees up.
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerError'
              examples:
                example-1:
                  value:
                    error:
                      type: rate_limited
                      code: throttled
                      message: Request was throttled. Expected available in 12 seconds.
                      request_id: req_5f0c3a9d2b7e4f18a6c1d9e3b2a7f4c8
                  summary: Illustrative response example
          description: Too many requests; wait for the number of seconds in Retry-After.
      security:
        - PartnerApiKey: []
      x-codeSamples:
        - lang: Shell
          label: curl
          source: >
            set -eu

            umask 077

            # Path values must be URL-encoded; query string excludes the leading
            question mark.

            url="${HD_BASE_URL:?Set API
            origin}/v1/partner/questionnaires/${HD_PATH_SLUG:?Set the path
            fixture}/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"

            printf 'Accept: application/json\n' >
            "${HD_REQUEST_HEADERS_FILE:?Private header file}"

            printf 'Authorization: Bearer %s\n' "${HD_PARTNER_API_KEY:?Set
            partner API key}" >> "$HD_REQUEST_HEADERS_FILE"

            curl --silent --show-error --max-time 30 --request GET \
              --header "@$HD_REQUEST_HEADERS_FILE" \
              --dump-header "${HD_HEADERS_FILE:?Private response header file}" \
              --output "${HD_RESPONSE_FILE:?Private response file}" \
              --write-out '%{http_code}\n%{content_type}\n' \
              "$url"
        - lang: Python
          label: requests
          source: >
            import os, json

            from pathlib import Path

            from urllib.parse import quote

            import requests

            os.umask(0o077)

            url = os.environ["HD_BASE_URL"].rstrip("/") +
            '/v1/partner/questionnaires/{slug}/'

            url = url.replace('{slug}', quote(os.environ['HD_PATH_SLUG'],
            safe=""))

            if os.environ.get("HD_QUERY_STRING"): url += "?" +
            os.environ["HD_QUERY_STRING"]

            headers = {"Accept": 'application/json'}

            headers["Authorization"] = "Bearer " +
            os.environ["HD_PARTNER_API_KEY"]

            kwargs = {}

            response = requests.request('GET', url, headers=headers, timeout=30,
            allow_redirects=False, **kwargs)

            Path(os.environ["HD_RESPONSE_FILE"]).write_bytes(response.content)

            Path(os.environ["HD_HEADERS_FILE"]).write_text(json.dumps(dict(response.headers)))

            print(response.status_code)

            print(response.headers.get("Content-Type", ""))
        - lang: JavaScript
          label: fetch
          source: >
            import { readFileSync, writeFileSync } from "node:fs";

            const env = process.env;

            let url = env.HD_BASE_URL.replace(/\/$/, "") +
            "/v1/partner/questionnaires/{slug}/";

            url = url.replace("{slug}", encodeURIComponent(env.HD_PATH_SLUG));

            if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;

            const headers = { "Accept": "application/json" };

            headers.Authorization = "Bearer " + env.HD_PARTNER_API_KEY;

            const options = { method: "GET", headers, redirect: "manual",
            signal: AbortSignal.timeout(30000) };

            const response = await fetch(url, options);

            writeFileSync(env.HD_RESPONSE_FILE, Buffer.from(await
            response.arrayBuffer()), {mode: 0o600});

            writeFileSync(env.HD_HEADERS_FILE,
            JSON.stringify(Object.fromEntries(response.headers)), {mode:
            0o600});

            console.log(response.status);

            console.log(response.headers.get("content-type") ?? "");
components:
  schemas:
    Questionnaire:
      type: object
      properties:
        slug:
          type: string
          description: Questionnaire slug.
        schema_version:
          type: string
          description: Version of the questionnaire schema.
        schema_hash:
          type: string
          description: >-
            The hash of exactly the schema in this response (localized when
            locale is set). Send it back with the submission: the current
            version's hash is accepted with or without a locale and in any
            supported locale; a stale hash is refused with 409 schema_outdated.
        locale:
          type: string
          nullable: true
          description: >-
            The locale the labels are written in: the requested one, or en when
            it is not supported. null when no locale was requested (labels carry
            message keys only).
        answer_format:
          allOf:
            - $ref: '#/components/schemas/AnswerFormatEnum'
          description: 'The answer format to submit: entries-v1.'
        upload_answer:
          allOf:
            - $ref: '#/components/schemas/UploadAnswer'
          description: How to answer an upload question.
        schema:
          type: object
          additionalProperties: {}
          description: 'The questionnaire: steps, questions, options and validation rules.'
      required:
        - answer_format
        - locale
        - schema
        - schema_hash
        - schema_version
        - slug
        - upload_answer
    PartnerError:
      type: object
      properties:
        error:
          allOf:
            - $ref: '#/components/schemas/PartnerErrorDetail'
          description: The error.
      required:
        - error
    PartnerFlagOffError:
      type: object
      description: >-
        The reduced body every route answers while the Partner API is switched
        off for the environment: no code, message or request_id.
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                - not_found
              description: Always not_found.
          required:
            - type
          additionalProperties: false
          description: Only the error type; no code, message or request_id.
      required:
        - error
      additionalProperties: false
    AnswerFormatEnum:
      enum:
        - entries-v1
      type: string
    UploadAnswer:
      type: object
      properties:
        value:
          type: string
          description: >-
            Answer an upload question with the upload_id returned by POST
            /files.
      required:
        - value
    PartnerErrorDetail:
      type: object
      properties:
        type:
          allOf:
            - $ref: '#/components/schemas/PartnerErrorDetailTypeEnum'
          description: >-
            Error type, for example validation_error or rate_limited; see Errors
            in the partner guides.
        code:
          type: string
          description: A stable, machine-readable error code.
        message:
          type: string
          description: A human-readable explanation; do not parse it.
        errors:
          type: array
          items:
            $ref: '#/components/schemas/PartnerErrorItem'
          description: Field-level problems, when any.
        request_id:
          type: string
          description: Quote this identifier when you contact support.
        current:
          allOf:
            - $ref: '#/components/schemas/SchemaVersion'
          description: >-
            On 409 schema_outdated: the schema_version and schema_hash currently
            published.
        current_notice_version:
          type: string
          description: >-
            On 422 consent_notice_outdated: the patient notice version to show
            and record.
      required:
        - code
        - message
        - request_id
        - type
    PartnerErrorDetailTypeEnum:
      enum:
        - api_error
        - authentication_error
        - conflict
        - invalid_request
        - not_found
        - permission_error
        - rate_limited
        - validation_error
      type: string
    PartnerErrorItem:
      type: object
      properties:
        field:
          type: string
          nullable: true
          description: The request field the problem refers to, or null when none.
        question_key:
          type: string
          nullable: true
          description: >-
            The questionnaire question the problem refers to, or null when the
            entry named none.
        code:
          type: string
          description: Machine-readable error code for this item.
        message:
          type: string
          description: Human-readable message. It never repeats the submitted value.
        min:
          type: number
          format: double
          description: 'On out_of_range: the lowest allowed value, when the schema sets one.'
        max:
          type: number
          format: double
          description: >-
            On out_of_range: the highest allowed value, when the schema sets
            one.
      required:
        - code
        - message
    SchemaVersion:
      type: object
      properties:
        schema_version:
          type: string
          description: The published schema_version.
        schema_hash:
          type: string
          description: A hash accepted for the published version.
      required:
        - schema_hash
        - schema_version
  securitySchemes:
    PartnerApiKey:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Partner API key sent as Authorization: Bearer <api key>. Test keys and
        live keys are separate.

````

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