The HTTP surface.
Routes and responses from api/openapi.yaml. Check your deployment’s CapabilityStatement to discover the FHIR behavior it actually supports.
This is a source reference, not a live connection to a healthcare server. Use synthetic data while Ilavrita is in development. Read current limitations.
GET/healthzLiveness
Reports that the process is up and serving.
Responses
200The server is serving requests.GET/versionBuild identity
Reports the running version and the commit it was built from. This is how a running instance is matched to a source tag and a release artefact.
Responses
200Build information.POST/auth/claimClaim this install
Spends the install's one claim token and makes the first Super Admin. An install is created with no members, so every route that resolves standing has nobody to resolve. This is the one request that brings standing into being, and it is not first-signup-wins: whoever reaches the port first is not who owns the host. What proves they do is a token minted when the install was created and written to `claim-token` in the data directory, readable only by the user the server runs as. It creates the identity as well as the membership, because there is no other way to have one. Whether the claim can succeed is settled before anything is written: this is the only route an install serves before anybody can authenticate, and one that created an identity for every request would be a way to fill a database from outside. Every way this can fail answers the same `403`. A token that is wrong, one that expired and one already spent are the same fact to whoever holds the wrong one, and saying which would say whether this install has been claimed.
Responses
201The install is claimed and the first Super Admin exists.400403500POST/auth/loginLog in
Proves a credential in one Project and issues a session. The token it returns appears here once and is never readable again: only its digest is stored. Every refusal answers the same way. A wrong password, an address nobody registered, a slug that resolves to nothing, an identity holding no standing, and a missing or wrong second-factor code are one answer, so nothing here can be used to find out which of them it was. `code` is carried with the password rather than asked for afterwards. A server that answered "now the code, please" would be saying the password was right, and would say it to anyone who guessed an address that exists.
Responses
200400401429500POST/auth/logoutLog out
Destroys the session the request carries. The stored token digest is removed rather than labelled, so the token matches nothing afterwards.
Responses
204The session is gone.401500GET/auth/sessionDescribe the current session
Reports who the token names, for a client that holds one.
Responses
200401500POST/auth/mfaEnrol a second factor
Begins a TOTP enrolment for the caller's own identity. The secret is returned once, here, and never again: what is stored afterwards is sealed, and what an authenticator app holds is the person's own. **Enrolling over a factor that is in force needs a current code from it.** Moving to a new phone and switching the factor off are the same request, and the code is what tells them apart — without it, a stolen session alone would be enough to disable the second factor, which is exactly what it exists to survive. A missing or wrong code answers `401`. The factor in force stays in force until the new one is proved at `/auth/mfa/activate`, so the move never leaves the account without one. Enrolling where nothing is in force needs no code: an unproved factor protects nobody, and asking for one would strand whoever's first attempt went wrong. There is no self-service way back from a lost phone while a factor is in force. That is deliberate — a self-service bypass would be the hole this rule closes. Somebody else with standing to administer the Project takes the factor off instead, through the control plane, which this description does not cover. `501` is the answer from a deployment that configured no sealing key. Storing the secret in the clear instead would make the database a list of everyone's second factor.
Responses
201400401500501DELETE/auth/mfaWithdraw a second factor
Removes the caller's factor. A current code is required to do it: a session is a bearer credential, so without one, stealing a session would be enough to take the second factor off the account it protects. A pending factor needs no code — the phone that would produce it may be exactly what was lost.
Responses
204The factor is gone.400401404500501POST/auth/mfa/activateActivate a second factor
Proves whatever is awaiting proof and puts it in force: a first enrolment, or a replacement begun at `/auth/mfa`. This is the only way a factor becomes required and the only way one is replaced — nothing can put a factor between a person and their account, or swap the phone that answers for it, except a code from that phone. `409` is the answer when nothing is awaiting proof. A code is accepted once. Presenting the same one again is refused, so a code watched over somebody's shoulder is not good for the rest of its thirty seconds.
Responses
200400401404409500501POST/fhir/R4Bundle transaction or batch
Performs every entry of a `transaction` Bundle as one act: all of them happen, or none does. An entry that fails rolls away every row, audit record and notification the entries before it wrote. Entries are performed in R4's order — deletes, then creates, then updates — but every identity is settled before any row is written, so an entry may reference one another entry creates whichever order the two were submitted in. A `urn:uuid:` fullUrl is the placeholder a reference names; one no entry claims is left as it was rather than rewritten to nothing. Every entry is decided on its own against the caller's grants, and every entry that wrote something is audited on its own. A Bundle is not a way to perform an interaction the caller could not have performed one at a time, nor a way to write without a trail. A `PUT` at an identity nothing holds creates it, which is what `update` does everywhere else here. An entry may state `ifNoneExist`, which is the conditional create, and a reference written `Patient?identifier=…` is resolved to the resource it identifies before anything is stored. The remaining per-entry preconditions are refused rather than ignored. A `batch` is submitted the same way and settles the opposite way: every entry stands or falls on its own, inside a savepoint, and the response describes each one including the ones that failed. It answers `200` whatever the entries did, because the request succeeded — which entry did not is the answer, not the status.
Responses
200Every entry was performed and the transaction committed. The answers
are in the order the entries were submitted, not the order they ran.
400401403404406409413415500GET/fhir/R4/metadataCapabilityStatement
Declares what this server actually supports. The statement is generated from the table the FHIR routes are registered from, so it can name no interaction the router does not dispatch; a resource appears only once the matching interactions are implemented and covered by tests. It names no resource and touches no storage, so it is the one FHIR route reachable before a caller is identified.
Responses
200The server's capability declaration.400406PUT/fhir/R4/{resourceType}Conditional update
Replaces the one resource the query matches. The query is the condition: `PUT /fhir/R4/Patient?identifier=…`. It is run under a grant for *searching* that type, not writing it. Naming a resource by a condition is reading it, so a caller who may write a type and not search it cannot address one this way. Nothing matching is a create, which is what `update` does everywhere else here. More than one match is `412`: the client asked about a resource and there are several, so nothing can be done that is what they meant. No query at all is `400`, because without one this would act on every resource of the type.
Responses
200201400401403406412413415500DELETE/fhir/R4/{resourceType}Conditional delete
Removes the one resource the query matches: `DELETE /fhir/R4/Patient?identifier=…`. Nothing matching is answered as done — the client wanted no resource matching, and there is none — so a retry after a successful delete is the ordinary case rather than an error. More than one match is `412`. `conditionalDelete` in the CapabilityStatement reads `single`, because a server that removed every match would be answering a different request.
Responses
204There is no resource matching that query, either because one was
removed or because there was never one.
400401403406412500GET/fhir/R4/{resourceType}Search
Returns a `searchset` Bundle of the resources this caller may reach that match every parameter given. Values separated by commas are alternatives; repeating a parameter requires both. Which parameters a type answers is declared in its `CapabilityStatement` entry. A parameter this server does not implement is refused with `400` rather than ignored: a search that silently dropped one would return more than it was asked for, and the caller could not tell. `total` is present only when `_total=accurate` asked for it, and a `next` link only when a further page exists.
Responses
200400401403404406500POST/fhir/R4/{resourceType}Create
Stores a new resource under a logical id this server assigns. A body carrying an `id` is refused: on this route the server mints them. The response always carries `ETag` and `Last-Modified` for the version written, and `Location` naming that version. `Binary` also accepts the document itself. Send it under its own media type — `application/pdf`, `image/png`, anything — and name what governs access to it in `X-Security-Context`, because a PDF has nowhere else to say it. A Binary naming nothing lands in no compartment and a confined caller cannot write it, which is the answer an unattributed document should get. However a Binary arrives, its bytes are kept outside the database and the row records what they are. A row carrying megabytes would make every read of the metadata pay for them and every backup carry them.
Responses
201400401403404406409413415500POST/fhir/R4/{resourceType}/$validateValidate a resource without storing it
Answers what this server would make of a resource. Nothing is stored, and `200` is the answer whichever way it went: the operation was performed, and what it found is the `OperationOutcome`. A client reads the issues rather than the status, which is what lets one response carry a warning alongside a pass. Each issue names the element it is about in `expression`. Two sets of rules are applied. The ones that hold for every R4 resource whatever its own definition says — no null, no empty string, no empty array, an `id` that is the `id` datatype, a relative reference that names an R4 type and an id — and the ones that come from the type's own base definition, which this build embeds and seeds: elements nobody declared, required elements that are absent, repeating elements written as values, choices written twice or under a type they do not permit, and primitive syntax. What is not checked is a profile, a terminology binding, a FHIRPath invariant or whether a reference resolves. `"status": "banana"` passes. A resource that passes may still be clinically nonsensical. The same rules gate `POST` and `PUT`: a resource this refuses is one those routes refuse with `400`, carrying the same issues. It is authorized as a write of that type. Nothing is stored, but validating is what a client does before writing.
Responses
200What validation found, as an `OperationOutcome`.400401403404413500POST/fhir/R4/{resourceType}/_searchSearch (posted)
The same search as the type-level `GET`, with the parameters in a form-encoded body. R4 offers it so a query too long or too sensitive for a URL is not one a client has to shorten; the two resolve identically. Parameters given in the URL are read as well, so a client may split them. The body must be `application/x-www-form-urlencoded`: this is the one route that reads a form, and a resource submitted as one is refused.
Responses
200400401403404406415500GET/fhir/R4/{resourceType}/_searchSearch (wrong method)
`_search` is a reserved segment, not a logical id. Every method the instance route answers is refused here, so none of them reads it as one.
Responses
501PUT/fhir/R4/{resourceType}/_searchSearch (wrong method)
The reserved segment is not a logical id, so an update to it is unimplemented.
Responses
501DELETE/fhir/R4/{resourceType}/_searchSearch (wrong method)
The reserved segment is not a logical id, so a delete of it is unimplemented.
Responses
501GET/fhir/R4/_searchWhole-system search (not implemented)
Searching across every type is not implemented. The route exists so the reserved segment answers as an unimplemented interaction rather than being read as a resource type nobody declared.
Responses
501POST/fhir/R4/_searchWhole-system search (not implemented)
Searching across every type is not implemented.
Responses
501GET/fhir/R4/_historyWhole-system history (not implemented)
The history of every resource is not implemented. The route exists so the reserved segment answers as an unimplemented interaction rather than being read as a resource type nobody declared.
Responses
501POST/fhir/R4/_historyWhole-system history (not implemented)
The history of every resource is not implemented.
Responses
501GET/fhir/R4/{resourceType}/_historyType history (not implemented)
Only instance-level history is implemented. This route exists so that the reserved `_history` segment is answered as an unimplemented interaction rather than read as a logical id. Every method the instance route answers is refused here, not only `GET`.
Responses
501PUT/fhir/R4/{resourceType}/_historyType history (not implemented)
The reserved segment is not a logical id, so an update addressed to it is unimplemented rather than a write to a resource called `_history`.
Responses
501DELETE/fhir/R4/{resourceType}/_historyType history (not implemented)
The reserved segment is not a logical id, so a delete addressed to it is unimplemented rather than a write to a resource called `_history`.
Responses
501GET/fhir/R4/{resourceType}/{id}Read
Returns the current version, with `ETag` and `Last-Modified`. A resource that was deleted answers `410`; one that never existed, or that this caller's authorization cannot see, answers `404`. The two are deliberately indistinguishable for an out-of-scope resource, so an id cannot be probed for. Reading a `Binary` answers the document itself when the `Accept` names its media type, by name or by family, and the resource — with the bytes base64-encoded in `data` — otherwise. A wildcard answers the resource: this server answers JSON for `*/*` everywhere else, and a route that read a wildcard as "give me the document" would hand a browser a download where every other route hands it a resource. An `Accept` naming neither is `406`.
Responses
200400401403404406410500PUT/fhir/R4/{resourceType}/{id}Update
Replaces the resource, or creates it under the id the client named if it does not currently exist. `200` means an existing resource was replaced; `201` means one was brought into existence, and carries `Location`. `If-Match` carries one quoted version id, optionally weak, or `*`. A claim that disagrees with what the server observes is `412`, and nothing is written; so is any claim at all, `*` included, against an id that is absent or deleted, because there is no current representation to match (RFC 9110 13.1.1). An update carrying no `If-Match` names no version and replaces whatever the resource currently holds.
Responses
200201400401403404406409412413415500DELETE/fhir/R4/{resourceType}/{id}Delete
Makes the resource a tombstone. Reads of the id afterwards answer `410`, while its history stays readable. `If-Match` is honoured exactly as it is on update, `*` included.
Responses
204The resource is deleted. No body.400401403404406409410412500GET/fhir/R4/{resourceType}/{id}/_historyInstance history
Returns one page of the versions this caller may see, newest first, as a history `Bundle`. A resource whose history ends in a deletion still has a history, so this route answers `200` where a read of the same id answers `410`. 20 versions by default and at most 200, with a `next` link when more follow. `_cursor` is a version this server handed back. A count outside that range, or a cursor that is not a version, is refused with `400` rather than rounded down: a caller who asked for a thousand and silently got two hundred cannot tell a short page from the end of the history. `total` is present only on a history that fits in one page. Counting a longer one would be a second query over the same rows, and a total that was guessed is worse than one that is absent. Which interaction produced a version is read from where it sits in the history, so only the page holding the oldest version marks an entry as the `POST` that created the resource. `_since`, `_at` and `_list` are not implemented and are ignored.
Responses
200400401403404406500GET/fhir/R4/{resourceType}/{id}/_history/{vid}Version read
Returns one immutable version. The `ETag` names the version asked for, never the resource's current one. Reading the version that recorded a deletion answers `410`. This route is authorized as history, not as read: a caller may be able to read a resource and not its history, or the reverse.
Responses
200400401403404406410500GET/fhir/R4/{resourceType}/{id}/{operation}Not implemented
Every FHIR interaction other than the six above is unimplemented. Failures are returned as `OperationOutcome` rather than as a framework error, so a client only ever parses FHIR-shaped responses.
Responses
501Generated from source revision 9462213.
