Skip to main content
Every error the API answers has one of two shapes, decided by where the request went:
  • Under the FHIR base (/v1/fhir/r4), a FHIR OperationOutcome, sent as application/fhir+json. A FHIR client reads it like any other resource.
  • Everywhere else, an RFC 9457 problem, sent as application/problem+json.
Each service’s page lists the statuses that service answers for its own reasons. This page covers the ones every service shares.

The two shapes

A problem has these members: Problems may gain members over time. Ignore any member you do not recognise. An OperationOutcome says the same things in FHIR’s terms: each entry in issue carries a code from FHIR’s issue types and a details.text saying what went wrong.

Statuses every service shares

A 502, a 504, and some 503s come from a source behind a service: see when a source fails.

Invalid request

400. Something in the request is not valid: a parameter, its value, or the body. Outside the FHIR base, issues names each parameter at fault. On the FHIR base, the OperationOutcome names it. A parameter the path does not take, or one sent twice, is a 400 too: see parameters.

Unauthorized

401. No API key was sent, or the key sent was not recognised. The detail says which, and the response carries WWW-Authenticate: Bearer realm="api.haau3.com", with error="invalid_token" added when a key was sent. See Authentication.

Not found

404. Nothing is served at this path, or the record asked for does not exist. A path that does not exist anywhere is a 404 in the base’s shape: GET /openapi.json lists every path the API serves, and GET /v1/fhir/r4/metadata lists what the FHIR base serves.

Method not allowed

405. The path exists, but does not take the method sent. The Allow header lists the methods it does take, for example Allow: GET, HEAD, OPTIONS.

Not acceptable

406. The request asked for a format the API does not answer in. Every answer is JSON: see formats. On the FHIR base it is also the answer to a FHIR release this endpoint does not serve.

Unsupported media type

415. A body was posted without saying it is JSON. Send Content-Type: application/fhir+json (application/json is accepted too).

Rate limited

429. Your organization reached one of its plan’s limits, which take effect on 2026-11-01. Two can answer, each with a Retry-After header in seconds and a sentence naming the limit:
  • Requests a minute, on every plan. The wait runs to the start of the next minute, so it is at most 60 seconds. Send the request again after it.
  • Requests a month, on the Developer plan only. The wait runs to 00:00 UTC on the 1st of the next month, which can be weeks away, so don’t send it again automatically. Change plan to keep going now.
Each plan’s numbers are in limits.

Server error

500. The server failed to process the request. Send it again once, after a short wait. If it fails again, the request is one we have to fix.

Unavailable

503. The service cannot answer right now. When the response carries Retry-After, wait that many seconds before sending the request again. On the FHIR base, the OperationOutcome says what is unavailable: a source rate limiting requests is coded throttled (see when a source fails), and a source this deployment does not have is coded not-supported. Any request can get a 503 with Retry-After when its API key could not be checked just now, coded transient on the FHIR base. So can a Scheduling request when the scheduling database is unavailable or slow. Neither says anything about the request: send it again after the wait.

When a source fails

Four services answer from a source behind them: terminology (LOINC), the provider directory (CMS’s National Provider Directory), Questionnaires (LOINC, and a form’s own publisher) and Plan Benefits’ $in-network (the provider directory). When the source fails, the service says so in an OperationOutcome. It never answers with a guess, an empty result or “not found” in place of the answer it could not get. Before answering any of these, the service has already asked its source a second time when that could help, after waiting a moment, and waited as long as the source asked when that was 5 seconds or less. Inside a batch Bundle, a throttled entry answers 503 but carries no Retry-After, because an entry has no headers of its own. Wait 2 seconds and send that entry again.

Methods

  • 405 and Allow. A method a path does not take answers 405 with an Allow header naming the methods it does take.
  • HEAD answers exactly like GET on every path that takes GET, without the body.
  • OPTIONS answers 204 with the Allow header and no body. Like every request under /v1, it needs an API key.
Inside a batch Bundle, an entry gets the answer the same request would get on its own. A 405 there has no header to carry, so its OperationOutcome names the methods instead.

Formats

Every answer is JSON. A request whose Accept header admits no JSON gets a 406 rather than JSON it did not ask for. These admit JSON: application/json, application/fhir+json, application/json+fhir, text/json, application/* and */*, with any parameters. No Accept header at all is fine too. On the FHIR base, _format takes precedence over Accept: json, application/json, application/fhir+json, application/json+fhir and text/json are served, and any other value is a 406. A POST to the FHIR base must say its body is JSON, with Content-Type: application/fhir+json or one of the types above. A body sent without one gets a 415.

Limits

Every API on the platform today is included in every plan. Free to start on the Developer plan. From 2026-11-01, each plan has two limits, counted for your organization across all of its keys: A month is a calendar month in UTC. A request counts once, a batch included. A 5xx, a 401 and a 429 do not count. If an organization keeps going past its allowance, we’ll get in touch about a plan that fits. The minute limit counts every request sent with your organization’s keys, and a request past it is a 429 on every plan (see rate limited). Until 2026-11-01 no request is refused for a limit. Your organization’s count for this month is on the dashboard’s Overview and Usage pages at platform.haau3.com, and the plans are on pricing. Every request also has bounds of its own, whatever the plan: The largest batch most callers need is far smaller: a batch of 50 lookups is a few kilobytes. A source or store that does not answer in time is a 504. When your API key could not be checked, or the scheduling database is unavailable or slow, the answer is a 503 with Retry-After (see unavailable). Neither says anything about your request, so send it again after a short wait. A first call after a quiet period can take a few seconds. Calls after it are faster.

Paging

A list that can be long comes back a page at a time, in one of four ways, each its service’s own: Every other list comes back whole in one answer: Scheduling’s /publishers, /service-types and /specialties, a Questionnaire search (with total), $counties and $in-network.

Caching and compression

Every JSON answer is compressed with gzip when the request’s Accept-Encoding allows it, and says so in Content-Encoding. Most HTTP clients ask for this and undo it without being told. The Slot Publisher’s NDJSON files are sent uncompressed.

Parameters

Each operation takes the parameters the API reference lists for it, and no others. A parameter it does not take gets a 400 that names it, rather than an answer as if it had not been sent, so a misspelt filter never comes back as an unfiltered answer that looks right. Every parameter at fault is named at once: outside the FHIR base, one entry in issues each, its path naming the parameter; on the FHIR base, one OperationOutcome issue each, its expression naming the parameter. The message lists the parameters the operation does take.
  • A parameter sent twice is refused the same way, unless the API reference says it repeats, as property does on CodeSystem/$lookup. Scheduling’s lists (serviceTypeCode, npi and specialty) are one parameter whose values are separated by commas.
  • A read (GET [base]/[type]/[id]), a batch and $reconcile-medications take no parameters. A batch entry carries its own in its request.url, and each entry is checked as the same request on its own would be.
  • _format belongs to the FHIR base and is taken on every path there (see formats). Outside the FHIR base it is not a parameter, and is refused like any other.
  • A Parameters body, when an operation is called with POST, follows the same rules: a parameter the operation does not take is refused, each parameter needs a name and a value, and a name sent both in the URL and in the body counts as sent twice.
Names are checked before values. Once every parameter is one the operation takes, a value that is not valid is named in the same way.

Request ids

Every response carries an X-Request-Id header holding a UUID that haau3 gave the request, errors included. A problem carries the same id as its instance, written urn:uuid:<id>. On the FHIR base the id is in the header only, and the OperationOutcome is unchanged. To ask about a response, email hello@haau3.com with its X-Request-Id and the time you sent the request. haau3 keeps its record of a request for 30 days, so ask within that time. You can also send an id of your own. Put a UUID in the request’s X-Request-Id header, and the response carries it back in X-Correlation-Id, beside haau3’s id. haau3 keeps your id with its record of the request, so a question can name either one. A value that is not a UUID is ignored: it is not kept, and it does not come back.

Changes

Changes a caller can see are recorded on the changelog from 2026-11-01.