Testing checklist
Use readiness checks to validate error and logging behavior.
Adapter reference
Correlate adapter requests and return stable partner error bodies.
v1.0ChartHero sends these headers on adapter endpoint calls. The Authorization
value is the stored external_api credential sent verbatim — it may be
Bearer <token>, ApiKey <token>, or a bare token, depending on what was
configured.
Authorization: Bearer <partner_issued_token>
X-ChartHero-Contract-Version: 1.0
X-ChartHero-Request-Id: req_01JZ123456789ABCDEFGHJKLM
Log X-ChartHero-Request-Id with endpoint, timestamp, environment, and outcome
only through your approved logging path so both teams can correlate support
requests. Do not place secrets, clinical data, or business identifiers in a
request ID.
Treat the request ID as an opaque correlation value, not an authorization token or a guaranteed PHI-free value. Some request IDs may originate in an upstream request context, and a token-shaped value does not prove safe provenance. Do not echo it into response bodies or copy it into support tickets, monitoring dashboards, or other systems unless your data-handling policy allows that destination.
All non-2xx adapter responses should use the standard error body.
{
"error_code": "permission_denied",
"message": "Actor cannot access this context.",
"retryable": false
}
Rules:
error_code is stable and machine-readable.error_code must be 128 characters or fewer and contain only letters, numbers, ., _, :, or -.message is safe for humans but is not a policy source of truth. Keep it non-empty and 512 characters or fewer.retryable tells ChartHero whether retry might succeed later.validation_details may be included for safe field-level validation details. Use at most 8 entries; keys are 64 characters or fewer and values are 256 characters or fewer.| HTTP | Error code | Meaning |
|---|---|---|
400 |
invalid_request |
Request shape or field validation failed |
400 |
unsupported_contract_version |
Requested contract version is not supported |
401 |
unauthorized |
Missing or invalid Authorization header value |
403 |
forbidden |
Authorization header value cannot use this endpoint |
403 |
permission_denied |
Actor cannot access the requested context |
404 |
patient_not_found |
Patient ID was not found |
404 |
appointment_not_found |
Appointment ID was not found |
404 |
external_user_not_found |
Verified email did not resolve to an external user |
409 |
identifier_mismatch |
Supplied IDs do not belong together |
409 |
email_ambiguous |
Verified email resolved to more than one external user |
429 |
rate_limited |
Partner endpoint rate-limited the request |
503 |
temporarily_unavailable |
Retryable outage or maintenance |
Error diagnostics is an optional, default-off receiver. When an organization enables it in ChartHero, ChartHero reports failures it observed at the HTTP boundary of a normal adapter call so you can find the matching request in your own logs.
ChartHero posts the diagnostic to a fixed path under the same configured base
URL, using the same Authorization value as every other adapter call.
POST /v1/diagnostics/errors
Implement the receiver before an organization turns the setting on. While the setting is off, ChartHero sends no diagnostics and adds no diagnostic header.
A diagnostic describes only what ChartHero's HTTP client directly observed about one adapter call. There are six codes and no others.
error_code |
Sent when | response_status |
|---|---|---|
transport_timeout |
ChartHero timed out sending the request or reading the response | null |
transport_error |
The request failed at connection, DNS, TLS, or the network | null |
http_error |
The final response status was outside 200-299, including 3xx, 401, and 403 |
Received status |
response_too_large |
A successful response exceeded ChartHero's 1 MiB limit | Received 2xx status |
response_content_type_invalid |
A successful response had a missing or non-JSON Content-Type |
Received 2xx status |
response_json_invalid |
A successful response was empty, invalid JSON, or not a JSON object | Received 2xx status |
A timeout or network error that happens while ChartHero is reading your response
body is still transport_timeout or transport_error with
response_status: null, even though your service already sent response headers
and a status.
ChartHero does not send a diagnostic for:
Only the final attempt of a call can produce a diagnostic, and one adapter call produces at most one diagnostic. Diagnostics is an informational signal, not an audit trail: absence of a diagnostic does not prove a call succeeded.
While diagnostics is enabled, ChartHero adds one extra header to every attempt of the source call.
X-ChartHero-Diagnostic-Id: diag_2f1dcb4ce6764fbeaa89fe86487b7e37
The same value arrives on the callback, in both the header and the body
diagnostic_id. It is the only value that links the two, and it always matches
^diag_[0-9a-f]{32}$. Log it next to your existing request log when it is
present, and treat it as optional — it is absent whenever diagnostics is
disabled for the installation.
The callback carries its own fresh X-ChartHero-Request-Id that identifies the
callback request only. It is never the source call's request ID, and ChartHero
never places a source request ID in the diagnostic body. Correlate with the
diagnostic ID; do not copy source request IDs into your diagnostic records.
POST /v1/diagnostics/errors
Authorization: Bearer <partner_issued_token>
Content-Type: application/json
X-ChartHero-Contract-Version: 1.0
X-ChartHero-Request-Id: req_01JZ123456789ABCDEFGHJKLM
X-ChartHero-Diagnostic-Id: diag_2f1dcb4ce6764fbeaa89fe86487b7e37
{
"diagnostic_id": "diag_2f1dcb4ce6764fbeaa89fe86487b7e37",
"source_endpoint": "/v1/launch/context",
"error_code": "response_json_invalid",
"message": "POST /v1/launch/context returned a successful response that was empty, invalid JSON, or not a JSON object. Return one JSON object matching the endpoint contract.",
"response_status": 200,
"occurred_at": "2026-08-06T15:42:18Z"
}
The body is strict and has exactly these fields.
| Field | Type | Meaning |
|---|---|---|
diagnostic_id |
string | Correlation value; matches ^diag_[0-9a-f]{32}$ and equals the X-ChartHero-Diagnostic-Id header |
source_endpoint |
string | The adapter route ChartHero was calling, such as /v1/launch/context; always a path, never a full URL, and never the diagnostics path |
error_code |
string | One of the six codes above; this is the stable field to automate on |
message |
string | Fixed ChartHero guidance for that endpoint, code, and status; 512 characters or fewer |
response_status |
integer or null | Received HTTP status, or null for the two transport codes |
occurred_at |
string | UTC completion time of the final source attempt, as an RFC 3339 timestamp ending in Z. Parse it leniently: it normally carries fractional seconds, as in 2026-08-06T15:42:18.123456Z |
message is generated from a closed ChartHero catalog. It is developer
guidance, not a policy source of truth, and ChartHero call sites cannot append
free-form text to it. Receiver implementations should require a JSON string
between 1 and 512 characters, but must not compare it with the catalog or
use its wording for authorization, routing, or acceptance. Automate on
error_code, source_endpoint, response_status, and diagnostic_id instead.
The body deliberately omits the source request ID, operation and capability keys, attempt count, retryability, your error code and message, validation details, and any free-form detail. It carries no request or response content, PHI, patient or encounter identifiers, credentials, URLs, or exception text.
Reject unknown top-level fields, and return 400 invalid_request when
X-ChartHero-Diagnostic-Id does not equal the body diagnostic_id.
Return exactly this on success. Any other status or body is a failed delivery.
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"accepted"}
The acknowledgement object is strict and has no other fields.
ChartHero makes one delivery attempt per eligible failure and never retries it. A rejected, slow, or failed diagnostic is dropped, and ChartHero does not send a diagnostic about a diagnostic. Never make your receiver call back into ChartHero or depend on delivery to complete the original workflow.
Acknowledge fast. ChartHero applies a short per-phase timeout to the callback, so do the durable work asynchronously rather than before responding.
You may use the standard error body for failures. ChartHero treats any non-200
status or non-matching body as a dropped delivery. Nothing in an error body can
make a delivery succeed, and ChartHero does not retry any of these.
| HTTP | Error code | Meaning |
|---|---|---|
400 |
invalid_request |
Header and body diagnostic_id differ, or the body shape is invalid |
400 |
unsupported_contract_version |
Requested contract version is not supported |
401 |
unauthorized |
Missing or invalid Authorization header value |
403 |
forbidden |
Authorization header value cannot use this endpoint |
429 |
rate_limited |
Receiver is rate-limited |
503 |
temporarily_unavailable |
Receiver is unavailable |
Log the diagnostic as a small structured record through your approved logging path.
| Log field | Include |
|---|---|
diagnostic_id |
X-ChartHero-Diagnostic-Id |
source_endpoint |
Adapter route from the body |
error_code |
Stable diagnostic code |
response_status |
Received status, or empty for transport codes |
occurred_at |
Reported source completion time |
request_id |
The callback's own X-ChartHero-Request-Id |
Log error_code rather than parsing message; the code is the stable field.
Do not log the Authorization header value. If you enrich the record by joining
it to stored request or response bodies, confirm your data-handling policy
allows that destination first.
Use readiness checks to validate error and logging behavior.
Inspect generated error schemas.
Review the current contract version.