Errors, revisions, and safe retries
Read both the HTTP status and response body. Log the operation ID, resource ID, HTTP status, and requestId. Redact tokens and patient data.
JSON error shape
Section titled “JSON error shape”{ "error": { "code": "revision_conflict", "message": "The package revision has changed.", "issues": [{ "field": "revision", "message": "Read the current revision." }] }, "requestId": "synthetic-request-id"}This is a synthetic shape example, not an actual response. Match stable error.code values, not translated messages. Field details can appear in error.field and error.issues[].field.
| HTTP | What to do |
|---|---|
400 |
Correct malformed JSON, path, query, or request shape |
401 |
Verify the token and host; obtain valid authentication |
403 |
Check scope, workspace selection, membership, and role |
404 |
Verify the resource ID and current workspace |
409 |
Read current state before deciding the next action |
413 |
Reduce the request to the documented body limit |
415 |
Use the expected content type |
422 |
Correct business or data validation issues |
428 |
Supply the required revision or idempotency precondition |
500, 502, 503 |
Preserve evidence; determine if an external action occurred before retrying |
An edge or network failure can return HTML rather than ApiError. Capture status and safe response headers, including X-Request-ID when present. Do not assume every error body is JSON.
Optimistic concurrency
Section titled “Optimistic concurrency”Read a package and retain its ETag. Use the exact value in If-Match for a revision. If the server returns revision_conflict, read the package again and resolve the change. Never overwrite another revision with a guessed ETag.
Validation belongs to one revision and context. An edit invalidates it. A previously valid package can require a new check if its referenced document or context changes.
One key per intent
Section titled “One key per intent”Use Idempotency-Key where required by the operation. The key must contain 8–128 allowed characters. UUID strings are suitable.
Store the key before you send the request, alongside the exact request bytes and target operation. For a retry, keep the same key, actor, path parameters, and body. With raw JSON input, keep the same bytes.
| Situation | Safe action |
|---|---|
| Response received and intent complete | Store the result; do not create another intent |
| Lost response | Read the resource; retry only the same intent with the same key when state permits |
idempotency_key_reused |
Stop: the key was used for different content |
idempotency_in_progress |
Wait and inspect state |
submission_outcome_unknown or provider_outcome_unknown |
Reconcile with external evidence |
| New, deliberately different business intent | Create a new key |
The contract does not publish an expiry window for reusing confirmed receipts. Never assume an old key becomes available. A new key cannot bypass an Unknown package or provider state.
Unknown does not mean failed
Section titled “Unknown does not mean failed”A timeout can occur after the external system received a request. Keep Unknown blocked until reconciliation. For RIPS, provide a known CUV and attempt ID. For health documents, use the reconciliation operation described in that document’s reference.
Use bounded backoff for reads. The current contract does not promise webhooks or a fixed polling interval. Honor 429 and Retry-After if they occur. These are technical controls; they do not create per-request charges.