Error handling
The JSON shape of every Symptom & Illness Check API error, the 401 rules, a code-by-code table of what went wrong and what to do, and which requests are safe to retry.
Every Symptom & Illness Check API error tells you two things — whether you can fix it (customType) or whether something failed on XUND's side (details.errorCode) — and handling that distinction correctly covers most of what an integrator needs. This page is the single reference for the error body shape, the authentication rules, the error codes, and retry guidance.
The error response shape
Errors are JSON bodies with up to three fields:
{
"error": "Forbidden",
"customType": "OVERVIEW_CAN_NOT_BE_SKIPPED",
"details": {
"description": "It is not possible to confirm the overview without getting it"
}
}| Field | Meaning |
|---|---|
error | The standard HTTP status name (Bad Request, Unauthorized, Forbidden, Not Found, Internal Server Error, ...). Always present. |
customType | A machine-readable error code. Present when the problem was caused by how the API was called — this is the field to branch on programmatically. |
details.description | An English explanation intended for developers. Do not show it to end users, and do not match on its text — it can change. |
details.errorCode | An internal code, typically on 5xx responses — usually XAG-prefixed, but not always (some 5xx paths carry other values, such as INVALID_REQUEST). Treat it as opaque: the request did not fail because of your input; include the code when contacting XUND support. |
details.data | Optional extra context, for example the individual violations behind an INVALID_REQUEST schema-validation failure. |
The set of customType values is open: new codes can appear without prior notice, so treat any unknown code generically (log it, show a generic error) rather than failing hard.
Authentication errors
Every request authenticates with an authorization: Bearer <token> header — see Authentication for obtaining tokens.
| Code | Status | When it happens |
|---|---|---|
API_KEY_OR_TOKEN_MISSING | 401 | No authorization header was sent. |
INVALID_API_KEY | 403 | History metadata was requested for a check that belongs to different credentials. |
INVALID_TOKEN | 401 | The Bearer token is malformed, has a bad signature, or has expired. |
INSUFFICIENT_SCOPES | 403 | The token is valid, but your credentials are not enabled for this endpoint. Contact XUND. |
Tokens expire, and there is no refresh-token flow for the client-credentials grant — on INVALID_TOKEN, request a fresh token from the token endpoint described in Authentication and repeat the request. A robust integration treats any 401 as "re-authenticate, then retry once".
Error codes
The most common codes, what triggers them, and how to react. Statuses: codes in the check-lifecycle and flow groups are 400 unless noted.
| Code | When it happens | What to do |
|---|---|---|
MISSING_CHECK_ID | Reserved for a missing check-id header — but in practice you'll see INVALID_REQUEST instead: the required-header check is part of request schema validation, which rejects the request first (the missing header shows up among the violations in details.data). | Send the checkId from initialization as the check-id header. |
CHECK_NOT_FOUND (404) | No check exists with the given id, or it belongs to different credentials. | Verify you stored the checkId correctly and are using the same credentials that created it. |
CHECK_IS_FINISHED (403) | The check is finished — the report was already fetched (fetching it closes the check), or the check ended via the no-diagnosis path. Answer, overview, and other write endpoints no longer accept its id. | The report, question, and status GETs and the history endpoints still work for a finished check; to run another check, initialize a new one. |
INVALID_REQUEST | The request body or parameters failed schema validation (details in details.data), or a required parameter combination is missing. | Fix the request shape; compare against the endpoint's reference page. |
INITIALIZER_SYMPTOM_NOT_FOUND | Symptom check initialization received a mainSymptomId that does not identify a known symptom. | Use a symptom id from the Content Library. |
QUESTION_WAS_ALREADY_ANSWERED | The questionId sent to POST /v1/chatbot/answer is not the last asked question. | Only the current question can be answered; use the overview edit endpoints to change earlier answers. |
FUNCTION_QUESTIONS_CANNOT_BE_ANSWERED | A FUNCTION-type question (overview or report) was sent to the answer endpoint. | Use the function's own endpoints instead — see The overview and The report. |
INFO_QUESTIONS_CAN_NOT_BE_ANSWERED | An INFO-type question was answered with a value instead of being skipped. | Send a skip answer (only the questionId). |
INVALID_ANSWER | The answer does not fit the question — for example an option id that is not in the question's option list, a value where an id is expected, a NUMBER value outside detailedTypeData.numberConstraints, or a TEXT value that fails answerValidatorRegex. | Send an option id from options.values, or a value that satisfies the question's constraints (numberConstraints, answerValidatorRegex) — validate locally before sending. |
INVALID_ANSWER_VALUE | The value sent for an AGE question is not numeric. | Send the age as digits only. |
INVALID_SKIP | A non-skippable question (isSkippable: false) was skipped. | Send a real answer. |
INVALID_SKIP_FORMAT | A skip request contained more than the questionId. | For skipping, send only the questionId. |
SAME_ANSWER_ALREADY_SET | The selected option is already selected on that question. | Treat as a no-op or refresh the question state first. |
INVALID_QUESTION_ID | The referenced question is not part of the check (on edit), or is not the last asked question (on DELETE /v1/chatbot/answer). | Use a questionId the check has actually asked. |
QUESTION_NOT_FOUND | The questionId sent to restoreToQuestion (or a question lookup) is not part of the ongoing check. Status 400 on restore, 404 on lookups. | Use an already-asked question's id. |
QUESTION_NEEDS_TO_BE_ANSWERED_BEFORE_RESTORE | restoreToQuestion targeted the currently asked question. | You can only restore to questions that have already been answered. |
QUESTION_NEEDS_TO_BE_ANSWERED_BEFORE_EDIT | An overview edit targeted the currently asked question. | Answer it through the normal loop instead of editing. |
INVALID_OPERATION | DELETE /v1/chatbot/answer was used on a question that is not EXTENDING-type. | Deselecting answers works only for EXTENDING questions. |
INVALID_ANSWER_TO_DELETE | The answerId sent to DELETE /v1/chatbot/answer is not a currently selected option of that question. | Only selected options can be deselected. |
PAYLOAD_TOO_LARGE | The batch answer edit payload had more than 100 elements. | Split the batch. |
OVERVIEW_NOT_AVAILABLE (403) | An overview endpoint was called while the check is not at the OVERVIEW function — before it is reached, or after it has already been passed. | Use the overview endpoints only while the FUNCTION question with detailedType: "OVERVIEW" is the current question. |
OVERVIEW_CAN_NOT_BE_SKIPPED (403) | POST /v1/chatbot/overview/confirm was called before GET /v1/chatbot/overview. | Fetch and display the overview first — see The trap: confirm requires a prior fetch. |
REPORT_NOT_AVAILABLE (403) | A report endpoint was called while the check is not at the REPORT function — before it is reached, or after it has already been passed. | Keep answering questions until the report FUNCTION question appears, then fetch the report. |
SYMPTOM_NOT_FOUND (404) | Symptom-related questions were requested for a symptom that is not part of the check. | Use an id from the overview's addedSymptoms. |
CONTENT_LIBRARY_ITEM_NOT_FOUND (404) | Illness details were requested with an unknown illnessId. | Use an id returned by a Content Library list endpoint. |
COUNTRY_CODE_OR_LOCATION_IS_MANDATORY | GET /v1/services/filters was called with neither a countryCode nor a location — at least one is required. | Send at least one of the two; if both are sent, countryCode silently takes precedence. |
UNSUPPORTED_COUNTRY | A Medical Services endpoint received location coordinates that resolve to a country XUND does not support. (A countryCode outside the supported set never gets this far — it fails schema validation as INVALID_REQUEST.) | Use a location in a supported country. |
Initialization failures and other 500s
A 500 body has no customType; instead details.errorCode carries an internal code (usually XAG-prefixed). Two initialization cases are worth knowing:
- Symptom check (initializeSymptomCheck): if even one provided FHIR observation's UMLS code cannot be mapped to a XUND symptom, the request fails with status code 500. An unknown
mainSymptomId, by contrast, is a 400 (INITIALIZER_SYMPTOM_NOT_FOUND). - Illness check (initializeIllnessCheck): if the FHIR condition's ICD-10 code is not known to the Content Library, the request fails with status code 500.
Retry guidance
The rule of thumb: 4xx means your request is wrong — fix it, don't resend it unchanged; 5xx means something failed on XUND's side — retrying an idempotent request is reasonable.
- Safe to retry: all GET endpoints (question, status, overview, report, lookups). Their responses stay consistent across calls, so retrying after a timeout or 5xx is harmless — but note they are not all stateless: the first GET /v1/chatbot/report (or /report/pdf) finalizes the check; retrying it simply serves the same report from the now-closed check.
- Not safe to retry blindly: PATCH /v1/chatbot/overview/answer/batch. It is applied sequentially and non-atomically — after a 500, some of the edits are already in place, so re-sending the same payload can fail again or re-apply edits. Re-read the check's state (for example GET /v1/chatbot/status) and send only what is still missing.
- Retry after re-authenticating: a 401 is not a transient failure; get a fresh token first (see above).
- Escalate instead of retrying: a 4xx with a
customTypewill keep failing until the request changes. A persistent 5xx with the sameerrorCodeis a bug or content issue on XUND's side — report it with theerrorCodeand, where relevant, thecheck-id.
Last updated on