> ## 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.

# Create a checkout session

> Creates a Stripe checkout session for a treatment, or returns an existing consultation already covered by an active plan.

`plan` picks what is bought: `one_off` (the default when omitted) is a single consultation with no renewal; `subscription` is the ongoing plan. Weight loss and hair loss accept either — the Hi-Doctor app pre-selects the plan, and their one-off is a single review whose prescription covers 30 days of treatment for hair loss. Every other category is `one_off` only, and asking for a plan there is a `400` (`subscription_not_available_for_treatment`).

Refuses with `PROFILE_INCOMPLETE` if the profile is missing required fields.

### Access, effects and retries
**Ownership:** Patient, completed relevant questionnaire/profile and own brand.

**Prerequisites:** An authenticated patient account is required.

**Effects:** Creates provider checkout session and local checkout row; can emit attribution event.

**Retries:** No advertised idempotency key; duplicate request may create another session. Never complete payment.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/users/checkout/
openapi: 3.0.3
info:
  title: Hi Doctor API
  version: 1.0.1
  description: >-
    The patient-facing Hi Doctor API supports account management, consultation
    questionnaires, medical documents, messages and billing. Sign-in requires
    X-Brand-Slug: hi-doctor; authenticated requests use Authorization: Bearer
    <access> with a JWT access token. See
    https://docs.hi-doctor.ai/api-reference/introduction for integration 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: Account
    description: Register, verify, authenticate and manage the signed-in account.
  - name: Questionnaires
    description: Discover, save and submit consultation questionnaires.
  - name: Consultations
    description: Read consultation state and manage consultation photographs.
  - name: Prescriptions
    description: Retrieve issued prescription documents and request reissue.
  - name: Messages
    description: Read and send messages to the medical team, and download attachments.
  - name: Billing
    description: >-
      Create checkout, manage subscriptions and retrieve
      payments/invoices/credit notes.
  - name: Records
    description: Read orders and manage patient health documents.
  - name: Safety reports
    description: Submit and read suspected side-effect reports; no automated diagnosis.
  - name: Progress
    description: Record and review eligible weight-loss progress.
  - name: Referrals
    description: Read the signed-in account's referral summary and referral activity.
paths:
  /v1/users/checkout/:
    post:
      tags:
        - Billing
      summary: Create a checkout session
      description: >-
        Creates a Stripe checkout session for a treatment, or returns an
        existing consultation already covered by an active plan.


        `plan` picks what is bought: `one_off` (the default when omitted) is a
        single consultation with no renewal; `subscription` is the ongoing plan.
        Weight loss and hair loss accept either — the Hi-Doctor app pre-selects
        the plan, and their one-off is a single review whose prescription covers
        30 days of treatment for hair loss. Every other category is `one_off`
        only, and asking for a plan there is a `400`
        (`subscription_not_available_for_treatment`).


        Refuses with `PROFILE_INCOMPLETE` if the profile is missing required
        fields.


        ### Access, effects and retries

        **Ownership:** Patient, completed relevant questionnaire/profile and own
        brand.


        **Prerequisites:** An authenticated patient account is required.


        **Effects:** Creates provider checkout session and local checkout row;
        can emit attribution event.


        **Retries:** No advertised idempotency key; duplicate request may create
        another session. Never complete payment.
      operationId: users_checkout_create
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
            pattern: ^[A-Za-z0-9_-]{8,128}$
          description: >-
            Optional key that makes a retry safe. Generate one value per logical
            operation (8 to 128 ASCII letters, digits, underscores or hyphens)
            and resend the same value on every retry. A repeat of a completed
            request returns its original status with `Idempotent-Replayed: true`
            and a short body of `state`, `resource_ids` and `response_hash`;
            read the resources to recover their content. Reusing a key with a
            different request returns 422 `idempotency_key_reuse`; a request
            still running returns 409 `idempotency_in_progress`. Keys are kept
            for 30 days. See
            https://docs.hi-doctor.ai/api-reference/idempotency.
          example: op-7f3a9c2e-0b1d-4e5f-9a8b-1c2d3e4f5a6b
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CartCreate'
            examples:
              example-1:
                summary: Illustrative request example
                value:
                  items:
                    - treatment_id: mounjaro
                      dosage: 2.5 mg
                      quantity: 1
                  plan: subscription
                  language: EN
                  return_path: /account
              example-2:
                summary: Captured request example
                value:
                  items:
                    - treatment_id: mounjaro
                      dosage: 2.5 mg
                      quantity: 1
                  plan: one_off
                  language: EN
                  return_path: /account
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      provider:
                        type: string
                        description: Checkout provider.
                        enum:
                          - stripe
                      checkout_url:
                        type: string
                        description: Confidential hosted checkout URL.
                        format: uri
                      session_id:
                        type: string
                        description: Provider checkout session identifier.
                      expires_at:
                        type: integer
                        description: Checkout expiration as Unix seconds, or null.
                        nullable: true
                      plan:
                        type: string
                        description: Checkout plan when supplied by the selected flow.
                    required:
                      - provider
                      - checkout_url
                      - session_id
                      - expires_at
                  - type: object
                    properties:
                      provider:
                        type: string
                        description: Legacy checkout provider.
                        enum:
                          - shopify
                      checkout_url:
                        type: string
                        description: Confidential hosted checkout URL.
                        format: uri
                      cart_id:
                        type: string
                        description: Legacy cart identifier.
                      lines:
                        type: array
                        description: Provider cart line edges.
                        items:
                          type: object
                      checkout_transport:
                        type: string
                        enum:
                          - cart_permalink
                        description: >-
                          Present when the legacy provider uses the cart
                          permalink fallback.
                    required:
                      - provider
                      - checkout_url
                      - cart_id
                      - lines
                  - type: object
                    properties:
                      covered_by_subscription:
                        type: boolean
                        enum:
                          - true
                        description: The existing subscription covers this consultation.
                      consultation_id:
                        type: string
                        format: uuid
                        description: Covered consultation identifier.
                      plan:
                        type: string
                        enum:
                          - subscription
                        description: Existing subscription plan.
                      treatment_updated:
                        type: boolean
                        description: >-
                          Whether this request changed the consultation
                          treatment.
                    required:
                      - covered_by_subscription
                      - consultation_id
                      - plan
                      - treatment_updated
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    provider: stripe
                    checkout_url: https://example.invalid/scrubbed-url
                    session_id: <PROVIDER_ID>
                    expires_at: 1791309383
                    plan: subscription
                example-2:
                  summary: Captured response example
                  value:
                    provider: stripe
                    checkout_url: https://example.invalid/scrubbed-url
                    session_id: <PROVIDER_ID>
                    expires_at: 1791316539
          description: Hosted checkout created; payment has not been completed.
        '400':
          description: >-
            Validation failed. `items` is required and must not be empty; each
            item needs `treatment_id` and `dosage`. Per-item errors come back as
            an array positionally matching `items`.
          content:
            application/json:
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    items:
                      - This field is required.
                example-2:
                  summary: Illustrative response example
                  value:
                    code: subscription_not_available_for_treatment
                example-3:
                  summary: Illustrative response example
                  value:
                    code: subscription_not_available_for_brand
              schema:
                type: object
                description: >-
                  Error envelope emitted by the source branch or DRF validation.
                  `detail`, `error` and `code` depend on the branch; field
                  validation uses lists keyed by request field.
                additionalProperties: {}
        '401':
          description: >-
            No bearer token, or a token that is expired or malformed.
            Re-authenticate; do not retry the same token.
          content:
            application/json:
              schema:
                type: object
                description: >-
                  Error envelope emitted by the source branch or DRF validation.
                  `detail`, `error` and `code` depend on the branch; field
                  validation uses lists keyed by request field.
                additionalProperties: {}
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    detail: Authentication credentials were not provided.
        '403':
          description: The account lacks permission for this operation.
          content:
            application/json:
              schema:
                type: object
                description: >-
                  Error envelope emitted by the source branch or DRF validation.
                  `detail`, `error` and `code` depend on the branch; field
                  validation uses lists keyed by request field.
                additionalProperties: {}
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    detail: You do not have permission to perform this action.
        '409':
          description: >-
            `plan` is `subscription` and the patient already has a live plan for
            this category. Renew through the subscription endpoints instead of
            starting a second plan.
          content:
            application/json:
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    code: already_subscribed
              schema:
                type: object
                description: >-
                  Error envelope emitted by the source branch or DRF validation.
                  `detail`, `error` and `code` depend on the branch; field
                  validation uses lists keyed by request field.
                additionalProperties: {}
        '422':
          description: >-
            The Idempotency-Key was already used with a different request.
            Generate a new key only for a genuinely new operation.
          content:
            application/json:
              schema:
                type: object
                description: Idempotency error. `code` names the reason.
                properties:
                  code:
                    type: string
                    description: Machine-readable reason.
                required:
                  - code
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    code: idempotency_key_reuse
        '429':
          description: Source-defined error response; inspect the envelope and retry rules.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
                description: >-
                  Endpoint/DRF error envelope; field validation is keyed by
                  request field.
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    detail: Request was throttled.
        '500':
          description: Request failed with the documented source error envelope.
          content:
            application/json:
              examples:
                example-1:
                  summary: Illustrative response example
                  value:
                    detail: An error occurred while creating your checkout
                    code: checkout_create_failed
                    error_name: CHECKOUT_CREATE_FAILED
              schema:
                type: object
                description: >-
                  Error envelope emitted by the source branch or DRF validation.
                  `detail`, `error` and `code` depend on the branch; field
                  validation uses lists keyed by request field.
                additionalProperties: {}
      security:
        - bearerAuth: []
      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/users/checkout/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"

            printf 'X-Brand-Slug: hi-doctor\nAccept: application/json\n' >
            "${HD_REQUEST_HEADERS_FILE:?Private header file}"

            printf 'Authorization: Bearer %s\n' "${HD_ACCESS_TOKEN:?Set backend
            JWT}" >> "$HD_REQUEST_HEADERS_FILE"

            curl --silent --show-error --max-time 30 --request POST \
              --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' \
              --header 'Content-Type: application/json' \
              --data-binary "@${HD_REQUEST_FILE:?JSON fixture file}" \
              "$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/users/checkout/'

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

            headers = {"X-Brand-Slug": "hi-doctor", "Accept":
            'application/json'}

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

            kwargs = {}

            kwargs["json"] =
            json.loads(Path(os.environ["HD_REQUEST_FILE"]).read_text())

            response = requests.request('POST', 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/users/checkout/";

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

            const headers = { "X-Brand-Slug": "hi-doctor", "Accept":
            "application/json" };

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

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

            headers["Content-Type"] = "application/json";

            options.body = readFileSync(env.HD_REQUEST_FILE, "utf8");

            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:
    CartCreate:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CartItem'
          description: Items for this cart create.
        plan:
          allOf:
            - $ref: '#/components/schemas/PlanBb6Enum'
          default: one_off
          description: Plan for this cart create.
        language:
          type: string
          default: EN
          description: Language for this cart create.
        return_path:
          type: string
          maxLength: 500
          description: >-
            Relative app path used after returning from the provider. Maximum
            500 characters.
        session_id:
          type: string
          nullable: true
          maxLength: 64
          description: >-
            Identifier of the related session. May be null when unavailable.
            Maximum 64 characters.
        meta_snapshot:
          description: Meta snapshot for this cart create. May be null when unavailable.
          anyOf:
            - $ref: '#/components/schemas/MetaAttributionSnapshot'
            - enum:
                - null
              type: string
              nullable: true
        oai_snapshot:
          description: Oai snapshot for this cart create. May be null when unavailable.
          anyOf:
            - $ref: '#/components/schemas/OpenAiAttributionSnapshot'
            - enum:
                - null
              type: string
              nullable: true
      required:
        - items
    CartItem:
      type: object
      properties:
        treatment_id:
          type: string
          description: Identifier of the related treatment.
        dosage:
          type: string
          description: Dosage for this cart item.
        quantity:
          type: integer
          minimum: 1
          default: 1
          description: Quantity for this cart item.
      required:
        - dosage
        - treatment_id
    PlanBb6Enum:
      enum:
        - one_off
        - subscription
      type: string
    MetaAttributionSnapshot:
      type: object
      properties:
        consent:
          allOf:
            - $ref: '#/components/schemas/MetaConsent'
          description: Consent for this meta attribution snapshot.
        fbp:
          type: string
          maxLength: 500
          description: Fbp for this meta attribution snapshot. Maximum 500 characters.
        fbc:
          type: string
          maxLength: 500
          description: Fbc for this meta attribution snapshot. Maximum 500 characters.
      required:
        - consent
    OpenAiAttributionSnapshot:
      type: object
      properties:
        consent:
          allOf:
            - $ref: '#/components/schemas/MetaConsent'
          description: Consent for this open ai attribution snapshot.
        obref:
          type: string
          maxLength: 500
          description: Obref for this open ai attribution snapshot. Maximum 500 characters.
        oppref:
          type: string
          maxLength: 500
          description: >-
            Oppref for this open ai attribution snapshot. Maximum 500
            characters.
        checkout_event_id:
          type: string
          maxLength: 255
          description: Identifier of the related checkout event. Maximum 255 characters.
      required:
        - consent
    MetaConsent:
      type: object
      properties:
        marketing:
          type: boolean
          description: Whether marketing applies to this meta consent.
      required:
        - marketing
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````

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