Skip to content

Troubleshooting

Every error on this API — request-level and per-record alike — is a FHIR OperationOutcome, so there is one error shape to parse. The issue[].code is what you branch on; diagnostics is human-readable text meant for your support queue, not for pattern matching.

Ingestion is asynchronous, and that splits errors into two places:

  • On the POST — the request never became an ingestion. You get a 4xx with an OperationOutcome body.
  • On the status receipt — the request was accepted (202) but individual records inside it were rejected. Poll the status URL and read outcome.rejected[]; each entry carries its own OperationOutcome.

A rejected record never sinks the batch around it. accepted and rejected counts are per record, so a batch of 50 with one bad record lands 49 and reports one — repair and re-send just that one.

Status code Cause What to do
400 structure The body was not a JSON FHIR Bundle (resourceType is not "Bundle"). Fix the serialisation. Check you are sending the Bundle itself, not a wrapper object.
401 login Missing, invalid, or revoked API key. Check the Authorization: Bearer rgk_… header. If the key was revoked, a new one must be minted — keys cannot be un-revoked.
403 forbidden The key authenticated but lacks the scope the route requires. The submit endpoint needs ingest:write; the status endpoint needs ingest:read. Ask for the missing scope.
404 No ingestion with that id belongs to the caller’s practice. Check you are polling with the same key that submitted, and that the id came from the Content-Location header.
429 Rate limit exceeded (120 requests/minute per endpoint). Back off. Poll status on an interval rather than in a tight loop.

A partner feed that has not been let into the partner-EHR beta is rejected at the door with 403 and the diagnostic “This partner feed is not enabled for the partner-EHR beta.” — the key itself is valid, so re-minting it will not help. If every practice’s feed starts returning that at once, it is a partner-level gate, not a client bug: ask your representative to confirm your beta access and contract status.

These arrive in outcome.rejected[] on a 202-accepted submission.

code Diagnostic Cause and fix
required No mother Patient found in the course of care. No Patient resolved to the mother role. The role comes from meta.profile — check you are stamping the BFDR mother profile.
required No child Patient found in the course of care. Same, for the child/newborn/fetus profile. Every course of care needs at least one.
business-rule A course of care must contain exactly one mother Patient. Two or more mother-profiled Patients in one course of care. If you are sending several births in one Bundle, use batch mode — a Bundle whose entries are themselves Bundles, one course of care each.
value Patient id has no recognized BFDR role profile (mother or child/newborn/fetus). A Patient carries no meta.profile we recognise. Add the profile, or drop the resource if it is not part of the course of care.
required A retraction must name the record it withdraws in Bundle.identifier.value. A retraction identifies its target by chart number only — send it.

conflict always means the record on file was left unchanged. Nothing was partially applied, so it is safe to reconcile and re-send.

Diagnostic Cause and fix
Stale update ignored: this push is older than the version already on file… Your meta.lastUpdated / timestamp predates the stored version — usually out-of-order delivery or a replayed queue message. Do not retry unchanged. Re-send only if your copy is genuinely newer. Note that staleness is only decidable when both sides carry a stamp: send one, or you get no protection.
This amendment would make the record a duplicate of another record in the practice. The correction would collapse two distinct records (same mother + newborn date/time). Resolve the two records first; an amendment is never allowed to merge.
This birth has already been recorded in the registry from another source. The same birth arrived from a different feed. It was not accepted and the rest of the batch was unaffected. Contact BirthTracks support to resolve the attribution — this is a data-ownership question, not a client bug.
This amendment would make the record a duplicate of a birth already recorded… from another source. As above, reached via an amendment rather than a create.
code Cause and fix
not-found A retraction named a chart number with no record for the practice. Retracting an unknown record is rejected; retracting twice is a no-op, so this usually means the chart number is wrong.
duplicate A chart-number collision the amendment path did not absorb. Chart numbers are unique per practice — use one stable id per course of care.
exception The record could not be persisted for an unexpected reason. The rest of the batch still landed. Retry once; if it persists, send the ingestion id to support.

Two receipt fields routinely get mistaken for failures:

  • suspected_duplicate: true on an accepted record. The record landed. The registry fingerprints each course of care by the mother’s name and the newborn date/time and flags a likely repeat — including the same birth arriving from a different input path — for human review. Do not re-send, and do not treat it as a rejection.
  • amended: true with a version. A re-send under a chart number already on file is a correction, not a duplicate. The version advances and the previous values are kept as history.

See Duplicate handling and Amendments and retractions.

A partner feed is expected to deliver at least daily — that is the contract floor, whatever cadence you choose above it (see Sync cadence).

BirthTracks runs a silent-feed detector on every active partner feed. A feed with no successful ingest inside the silence window is flagged to us, and so is a feed that was provisioned but has never delivered at all — the blind case that otherwise hides, because a practice’s dashboard looks fine while its statistics quietly go stale. The alert carries partner, practice, key label, and timestamps only; it never contains record content.

What to watch on your side:

  • Time since your last completed ingestion per practice, not per partner. A single quiet practice is the common failure, and a healthy aggregate hides it.
  • Rejection rate per practice. A sudden climb usually means a mapping change on your side, not a change on ours.
  • Ingestions stuck at queued or processing. Poll to a terminal state (completed or failed) rather than assuming a 202 means landed — the 202 only means accepted for processing.

If a feed has gone quiet for a reason you expect — a practice offboarding, a planned pause — tell your representative so the key can be revoked or the partner suspended, rather than leaving a silent feed alerting.

When you contact support, include the ingestion id, the chart number (Bundle.identifier.value) of the affected record, and the OperationOutcome you received. Do not paste patient identifiers into a support ticket — the ingestion id and chart number are enough for us to find the record.