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

# Partner API errors: envelope and error codes

> The Hi Doctor Partner API error envelope, error types by HTTP status, per-question errors, request IDs and the full table of stable error codes.

Every Partner API error returns a JSON envelope with a stable `code` to branch on, a broad `type` tied to the HTTP status, a readable `message` and a `request_id`. This page is the single list of error codes, grouped by area, with what each one means. Fix the request on a `4xx` error, and retry `5xx` and network errors with backoff and the same `Idempotency-Key`.

Last reviewed: 2026-11-04

Other Partner API pages link here instead of repeating codes. Retry rules for submissions are in [Partner API idempotency](https://docs.hi-doctor.ai/partners/idempotency.md), and request limits in [Rate limits](https://docs.hi-doctor.ai/partners/rate-limits.md).

## What does an error look like?

Errors returned by the Partner API share this shape:

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "code": "upload_unusable",
    "message": "The upload does not exist, has expired or was already used.",
    "errors": [
      { "question_key": "body_photo", "code": "upload_unusable", "message": "The upload does not exist, has expired or was already used." }
    ],
    "request_id": "req_4f1d9c2a7b3e4d5f8a9b0c1d2e3f4a5b"
  }
}
```

| Field | Meaning |
| - | - |
| `type` | Broad category, tied to the HTTP status. |
| `code` | Stable machine-readable code. Branch on this, never on `message`. |
| `message` | Human-readable explanation in English. It can change; do not parse it. |
| `errors` | Optional list of item-level problems, each with a `code` and `message`. Answer problems carry `question_key` when the API can tie them to a step; range errors also carry `field`, `min` and `max`. Request-body problems (`invalid_request`) carry `field`, the path of the bad field, instead. |
| `request_id` | Identifier of the request. Send it to Hi Doctor when you ask about an error. If you send an `X-Request-ID` header, its value is used here after surrounding spaces are trimmed and it is cut to 64 characters. |

Some errors add a field with more context, for example `current` on `schema_outdated` and `current_notice_version` on `consent_notice_outdated`. Error responses never contain doctor notes or file names, and never repeat an answer value or identifier you sent: answer errors name the `question_key` with a fixed message, and a national identifier is never echoed. The one thing echoed back is a question key: when you answer a key that is not allowed, `errors[].question_key` is the key you sent.

Two responses fall outside this shape:

* When the Partner API is not available to your account, every route answers `404` with only `{"error": {"type": "not_found"}}`.
* An unexpected server failure can return a `500` without this envelope. Retry it like any `5xx`.

## Types

| HTTP status | `type` |
| - | - |
| 400 | `invalid_request` |
| 401 | `authentication_error` |
| 403 | `permission_error` |
| 404 | `not_found` |
| 405 | `invalid_request` |
| 409 | `conflict` |
| 422 | `validation_error` |
| 429 | `rate_limited` |
| Any other status returned in the envelope | `api_error` |

A `404` is also what you get for a resource of another partner or of the other mode: the API never confirms that such a resource exists.

## Codes

### Authentication and access

| Status | Code | Meaning |
| - | - | - |
| 401 | `not_authenticated` | No `Authorization` header was sent. |
| 401 | `invalid_token` | The header is not `Bearer <key>`, or the key is malformed or not valid. |
| 401 | `key_revoked` | The API key has been revoked. |
| 401 | `key_expired` | The API key has expired. |
| 401 | `partner_suspended` | The partner account is suspended. |
| 403 | `partner_not_live` | Live mode is not enabled for this partner. |
| 403 | `insufficient_scope` | The API key lacks a scope this endpoint requires. |
| 400 | `brand_header_not_allowed` | Brand headers are not accepted on partner requests; the brand comes from the API key. |

### Requests and pagination

| Status | Code | Meaning |
| - | - | - |
| 400 | `invalid_request` | A query parameter is malformed, for example a bad `limit` or date. |
| 422 | `invalid_request` | The request body or upload form is invalid, for example a missing field, a wrong type, an empty information-request answer or an unknown `to` value on a test advance. |
| 400 | `invalid_cursor` | The pagination cursor is not valid. |
| 404 | `not_found` | No such resource for this partner and mode. |
| 429 | `throttled` | Too many requests. See [Rate limits](https://docs.hi-doctor.ai/partners/rate-limits.md). |

### Idempotency

| Status | Code | Meaning |
| - | - | - |
| 400 | `idempotency_key_required` | `POST /v1/partner/consultations` needs an `Idempotency-Key` header. |
| 400 | `invalid_idempotency_key` | The key must match `[A-Za-z0-9_-]{8,128}`. |
| 409 | `idempotency_in_progress` | A request with this key is still executing. Retry shortly. |
| 422 | `idempotency_key_reuse` | The key was already used with a different request. |

### Submission

| Status | Code | Meaning |
| - | - | - |
| 422 | `product_unavailable` | The product, country, plan or dosage is not in your catalogue, or the country is closed to new consultations. |
| 409 | `schema_outdated` | The questionnaire schema version or hash is not the published one. |
| 409 | `context_unsupported` | The submission needs a context the Partner API does not offer. |
| 422 | `consent_required` | A consent flag is `false` (including `"false"` or `0`, which are read as `false`). Every flag must be `true`. A missing flag, or a value that cannot be read as a boolean, returns `422 invalid_request`. |
| 422 | `consent_notice_outdated` | The patient notice version is not the current one. |
| 409 | `duplicate_external_reference` | A consultation with this `external_reference` already exists. |
| 409 | `patient_conflict` | Live mode only: your brand already has an account with this email and the `external_patient_id` is not yet linked to a patient, or the `national_identifier` or `identity_document` differs from the one stored for the patient. |
| 409 | `consent_withdrawn` | The patient withdrew consent; no further consultations are processed for them. |
| 422 | `patient_identifier_required` | The patient's national identifier is required for this country. |
| 422 | `invalid_request` | With `errors[].code` `invalid_identifier`: the national identifier has an unknown type, a type the product country does not use, or an invalid value. With `errors[].code` `invalid_identity_document` (field `patient.identity_document`): the identity document breaks its rules, for example an unknown issuing state or a number not valid for its type and issuing state. A structural error keeps its nested field instead, for example `patient.identity_document.type` with `invalid_choice` or `patient.identity_document.number` with `required`. With `field` `patient.address`: `line1` and `line2` together exceed 200 characters. With `field` `patient.address.country` and `errors[].code` `residence_outside_eu`: the patient's country of residence is not an EU member state. |
| 422 | `unexpected_question_key` | An answer to a step that the other answers hide. One error item per hidden `question_key`. |
| 422 | `country_conflict` | The answers name a country other than the request's `country`. |
| 422 | `country_closed` | The consultation's clinical country, checked during submission, is not accepting new consultations. |
| 422 | `invalid_answers` | An answer does not match the questionnaire schema, or an answer entry has no `question_key`. |
| 422 | `out_of_range` | A value is outside the schema's input bounds. |
| 422 | `clinical_ineligibility` | The answers do not meet the clinical eligibility rules. |
| 422 | `pharmacy_selection_invalid` | A `pharmacy_selection` answer is not valid for the patient's country. |

### Files

| Status | Code | Meaning |
| - | - | - |
| 422 | `unsupported_media` | Only JPEG, PNG and PDF files are accepted, detected from their bytes. |
| 422 | `file_too_large` | The file exceeds 10 MB. |
| 422 | `image_too_large` | The image dimensions exceed the allowed limits. |
| 422 | `checksum_mismatch` | The `sha256` does not match the uploaded bytes. |
| 422 | `upload_unusable` | The upload does not exist, has expired or was already attached, or the same `upload_id` is used twice in one information-request answer. |
| 422 | `storage_quota_exceeded` | Your pending upload storage is full. Expired uploads no longer count. |

### Consultations, information requests and test mode

| Status | Code | Meaning |
| - | - | - |
| 409 | `not_cancellable` | The consultation can no longer be cancelled: `cancellable` is `false`. |
| 409 | `consultation_busy` | The consultation is busy; retry shortly. |
| 409 | `info_request_not_open` | The information request was already answered or withdrawn, including a request withdrawn when the patient withdrew consent. Checked before `consent_withdrawn`. |
| 409 | `case_closed` | The consultation is no longer awaiting information. |
| 409 | `consent_withdrawn` | The patient has withdrawn consent and this information request is still open. The withdrawal itself withdraws the requests open at that moment, so answers to those fail with `info_request_not_open` instead. |
| 422 | `unexpected_question_key` | An information-request answer entry names a question that was not re-asked, or no question. |
| 422 | `invalid_answers` | On an information-request answer: the same question is answered twice, or the merged answers do not match the schema. |
| 422 | `identity_document_required` | The information request has `reason_code` `identity_document_required` and the answer carries answers or uploads but no `identity_document`. |
| 422 | `invalid_request` | On an information-request answer: the body has no answer, upload or identity document (checked first), or lists more than 10 uploads; with `errors[].code` `invalid_identity_document` (field `identity_document`), the document is not valid; a structural error keeps its nested field, such as `identity_document.type` with `invalid_choice`; with `errors[].code` `unexpected_field` (field `identity_document`), the request does not ask for an identity document. On a test advance: `to` is not a status, or `reason_code` or `question_keys` has the wrong shape (for example `reason_code` `""`), whatever the target. |
| 409 | `patient_conflict` | On an information-request answer: the `identity_document` differs from the one stored for the patient. |
| 422 | `illegal_transition` | The test consultation cannot move from its current status to the requested one. Checked after the body's shape: an unknown target returns `422 invalid_request`. |

### Webhooks

| Status | Code | Meaning |
| - | - | - |
| 422 | `invalid_webhook_url` | The URL must be `https` on port 443, without credentials, at a public address. |
| 422 | `invalid_webhook_events` | `events` is a list of strings but is not `["*"]` or a non-empty list of documented event types. `endpoint.test` cannot be subscribed to. A value that is not a list, a blank entry or an entry longer than 48 characters fails first with `422 invalid_request`. |
| 422 | `mode_mismatch` | A key manages webhook endpoints in its own mode only. |
| 409 | `endpoint_disabled` | Enable the endpoint before sending a test event. |
| 503 | `webhooks_unavailable` | Webhook endpoints cannot be created, changed or given a new secret right now. Retry later. |

## How should you handle errors?

* `4xx`: fix the request. Do not retry unchanged, except `409 idempotency_in_progress`, `409 consultation_busy` and `429`.
* `5xx` and network errors: retry with backoff. For submissions, reuse the same `Idempotency-Key` and the same body so a retry within 24 hours returns the stored result instead of creating a second consultation. See [Partner API idempotency](https://docs.hi-doctor.ai/partners/idempotency.md).
* Unknown codes: new codes can be added and existing codes keep their meaning. Handle a code you do not know by its HTTP status and `type`.

## Frequently asked questions

### Should my code branch on the error code or the message?

On the code field. It is stable and machine-readable, while the message field is English text that can change.

### Which errors can I retry unchanged?

409 idempotency\_in\_progress, 409 consultation\_busy, 429 and 5xx errors. Fix every other 4xx error before you send the request again.

### What should I send Hi Doctor when I ask about an error?

The request\_id from the error. If you send an X-Request-ID header, that value is used as the request\_id.

### Why do I get 404 for a consultation I know exists?

A resource of another partner or of the other mode answers 404. Check that you use a key of the same mode, test or live, as the consultation.

### Can new error codes appear?

Yes. New codes can be added, and existing codes keep their meaning. Handle an unknown code by its HTTP status and type.

### Do error responses repeat patient data?

No. Errors never contain doctor notes or file names and never repeat an answer value or identifier you sent. The only value echoed back is a question key that was not allowed.

## Next steps

> [card] [Partner API idempotency](https://docs.hi-doctor.ai/partners/idempotency.md)
>
> Retry submissions safely with the same Idempotency-Key.

> [card] [Rate limits](https://docs.hi-doctor.ai/partners/rate-limits.md)
>
> Request limits and how to handle 429.

> [card] [Submit a consultation](https://docs.hi-doctor.ai/partners/submit-a-consultation.md)
>
> The request most submission errors come from.


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