FHIR RESTful API
Payerbox exposes the full base FHIR R4 RESTful API at /fhir — every interaction defined in the FHIR HTTP spec is available without per-resource configuration. This page is a summary of the surface; for full semantic detail, modifier reference, and edge-case behavior, see the Aidbox docs on REST API CRUD and FHIR Search.
The /fhir base uses FHIR-format resources. Aidbox also exposes a non-FHIR / base which uses Aidbox's native resource format — use /fhir for any FHIR-conformant integration.
Endpoints
| Interaction | Method | URL |
|---|---|---|
| Create | POST | /fhir/<type> |
| Conditional create | POST | /fhir/<type>?<search-params> or POST /fhir/<type> + If-None-Exist |
| Read | GET | /fhir/<type>/<id> |
| vread | GET | /fhir/<type>/<id>/_history/<vid> |
| Update | PUT | /fhir/<type>/<id> |
| Conditional update | PUT | /fhir/<type>?<search-params> |
| Patch | PATCH | /fhir/<type>/<id> |
| Delete | DELETE | /fhir/<type>/<id> |
| Conditional delete | DELETE | /fhir/<type>?<search-params> |
| Search | GET | /fhir/<type>?<search-params> |
| Transaction / batch | POST | /fhir/ |
Auth
SMART Backend Services and Client Credentials. The caller's OAuth client must be allowed (via Aidbox access policies) to perform the interaction on the target resource type. Any interaction may return 403 Forbidden if the client is not authorized. See Authentication.
Create
Endpoint
POST /fhir/<type>
Response codes
| Status | Cause |
|---|---|
| 201 Created | Resource created; Location, ETag, Last-Modified headers set |
| 400 Bad Request | Body could not be parsed or failed basic FHIR validation |
| 409 Conflict | Resource with the given id already exists (Aidbox respects id in the body) |
| 422 Unprocessable Entity | Body violated profile rules or server business rules |
Example
POST /fhir/Patient
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"name": [{"given": ["Bob"], "family": "Smith"}],
"gender": "male",
"birthDate": "1970-05-15"
}
HTTP/1.1 201 Created
Location: /fhir/Patient/<id>/_history/1
ETag: W/"1"
Last-Modified: 2026-05-15T13:40:00Z
{
"resourceType": "Patient",
"id": "<id>",
"name": [{"given": ["Bob"], "family": "Smith"}],
"gender": "male",
"birthDate": "1970-05-15",
"meta": {"versionId": "1", "lastUpdated": "2026-05-15T13:40:00Z"}
}
Conditional create
POST /fhir/<type>?<search-params>
Or pass the search criteria in the If-None-Exist header. Outcome depends on the number of matches:
- No matches — server performs a normal
create(201). - One match — server returns the existing resource (200), no new resource is created.
- Multiple matches —
412 Precondition Failed; the criteria were not selective enough.
The same ifNoneExist field is supported inside Bundle.entry.request for transaction bundles (case-sensitive — must be spelled exactly ifNoneExist).
Read
Endpoint
GET /fhir/<type>/<id>
Response codes
| Status | Cause |
|---|---|
| 200 OK | Resource returned |
| 404 Not Found | No resource with the given id |
| 410 Gone | Resource was deleted |
Example
GET /fhir/Patient/<id>
HTTP/1.1 200 OK
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"id": "<id>",
"name": [{"given": ["Bob"], "family": "Smith"}],
"gender": "male",
"birthDate": "1970-05-15",
"meta": {"versionId": "1", "lastUpdated": "2026-05-15T13:40:00Z"}
}
vread
GET /fhir/<type>/<id>/_history/<vid>
Returns a specific version of the resource. <vid> is the meta.versionId value (typically a monotonically-increasing integer).
Update
Endpoint
PUT /fhir/<type>/<id>
Replaces the resource. If no resource exists at <id>, one is created (upsert semantics). The id cannot be changed once the resource exists.
Response codes
| Status | Cause |
|---|---|
| 200 OK | Resource updated |
| 201 Created | Resource did not exist and was created |
| 412 Precondition Failed | If-Match versionId did not match the current version, or If-Match: * was sent and the resource does not exist |
| 422 Unprocessable Entity | Body violated profile rules or server business rules |
Example
PUT /fhir/Patient/<id>
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"id": "<id>",
"name": [{"given": ["Bob"], "family": "Smith"}],
"gender": "male",
"birthDate": "1970-05-15"
}
HTTP/1.1 200 OK
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"id": "<id>",
"meta": {"versionId": "2", "lastUpdated": "2026-05-15T13:41:00Z"},
"name": [{"given": ["Bob"], "family": "Smith"}],
"gender": "male",
"birthDate": "1970-05-15"
}
Conditional update
PUT /fhir/<type>?<search-params>
Outcome depends on the number of matches:
- No matches — server performs a
create. - One match — server performs the update against the matching resource (the
idin the request body is ignored). - Multiple matches —
412 Precondition Failed.
Versioned update (optimistic concurrency)
Send If-Match: <versionId> to require that the current meta.versionId matches. On mismatch the server returns 412 Precondition Failed with an OperationOutcome (code: conflict).
Send If-Match: * to update-only (refuse to create if the resource does not exist) — returns 412 instead of creating.
Patch
Endpoint
PATCH /fhir/<type>/<id>
Three patch dialects are supported — pick by Content-Type (or by the _method query parameter):
| Dialect | Content-Type | _method | Body shape |
|---|---|---|---|
| Merge Patch (RFC 7386) | application/merge-patch+json | merge-patch | JSON object with the fields to set / null out |
| JSON Patch (RFC 6902) | application/json-patch+json | json-patch | JSON array of {op, path, value} operations |
| FHIRPath Patch | — | fhirpath-patch | Parameters resource with operation parts |
If the dialect is not specified, Aidbox infers it: Parameters resourceType → FHIRPath Patch; array body → JSON Patch; otherwise → Merge Patch.
A successful patch fires the same update event as PUT. No-op patches (replacing a field with its current value) return 200 OK but do not fire an event.
Example
PATCH /fhir/Patient/<id>
Content-Type: application/merge-patch+json
{"active": false, "telecom": null}
PATCH /fhir/Patient/<id>
Content-Type: application/json-patch+json
[
{"op": "replace", "path": "/active", "value": false},
{"op": "remove", "path": "/telecom"}
]
PATCH /fhir/Patient/<id>
Content-Type: application/fhir+json
{
"resourceType": "Parameters",
"parameter": [{
"name": "operation",
"part": [
{"name": "type", "valueCode": "replace"},
{"name": "path", "valueString": "Patient.active"},
{"name": "value", "valueBoolean": false}
]
}]
}
Delete
Endpoint
DELETE /fhir/<type>/<id>
Response codes
| Status | Cause |
|---|---|
| 200 OK | Resource deleted (body contains the deleted resource) |
| 204 No Content | Resource already deleted, did not exist, or _no-content=true was passed |
| 404 Not Found | Resource type not registered |
| 412 Precondition Failed | If-Match versionId did not match the current version |
A subsequent GET /fhir/<type>/<id> returns 410 Gone.
Example
DELETE /fhir/Patient/<id>
HTTP/1.1 200 OK
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"id": "<id>",
"meta": {"versionId": "3", "lastUpdated": "2026-05-15T13:42:00Z"},
...
}
Conditional delete
DELETE /fhir/<type>?<search-params>
- No matches —
204 No Content. - One match — server performs a normal
delete. - Multiple matches —
412 Precondition Failedby default. To delete all matches, sendx-conditional-delete: remove-all.
Search
Endpoint
GET /fhir/<type>?<param1>=<value1>&<param2>=<value2>&...
Returns a Bundle of type: searchset with the matched resources in entry[].resource, a total count, and link[] entries for pagination (first / prev / self / next / last).
Search parameter types
Each search parameter has a type, declared on the SearchParameter resource, that drives matching semantics:
| Type | Matching | Example |
|---|---|---|
| string | Case-insensitive, accent-insensitive, starts-with by default | name=John |
| token | Exact match on a code or system|code pair | status=active, identifier=mysystem|123 |
| reference | Match by reference target | subject=Patient/123 |
| date | Range comparison with FHIR date prefixes (eq, ne, lt, le, gt, ge, sa, eb) | birthdate=ge2019-01-01 |
| number | Numeric comparison; same prefixes as date | probability=gt0.8 |
| quantity | Numeric value comparison only — unit specification not implemented | value-quantity=75.5 |
| uri | Exact, case-sensitive URI match | url=http://example.com |
| composite | Two-or-more values joined by $; matches only when both values land on the same element | code-value-quantity=loinc|12907-2$1 |
| special | Implementation-defined | _filter, _text, _content |
Modifiers
Append a modifier with a colon to refine the match (<param>:<modifier>=<value>):
| Modifier | Applies to | Description |
|---|---|---|
:missing | all | Field absent (true) or present (false) |
:exact | string | Exact string match (case- and accent-sensitive) |
:contains / :starts / :ends | string | Substring / prefix / suffix |
:text | all | Textual match against the field's text representation |
:not | token, uri, reference | Negate the match |
:in | token | Match within a ValueSet |
:of-type | token | Match identifier.type codes |
:i | token | Case-insensitive |
:identifier | reference | Match Reference.identifier instead of Reference.reference |
:iterate / :recurse | _include, _revinclude | Follow references recursively |
Common parameters
| Parameter | Effect |
|---|---|
_id | Match by resource id |
_lastUpdated | Match by meta.lastUpdated (supports date prefixes) |
_profile | Match by meta.profile |
_elements | Return only the listed top-level fields (comma-separated, prefix - to exclude) |
_count | Page size (default page) |
_page | Page number (1-based) |
_total | none / estimate / accurate — control the total count strategy |
_sort | Sort key(s); prefix - for descending |
_include | Include resources referenced from the matches |
_revinclude | Include resources that reference the matches |
_filter | Boolean expression for OR across different parameters |
_text / _content | Full-text search over narrative / resource body |
_include and _revinclude
_include=<source-type>:<search-param>[:<target-type>]
_revinclude=<source-type>:<search-param>[:<target-type>]
Append :iterate to follow the reference chain recursively.
GET /fhir/Patient?_include=Patient:organization
GET /fhir/Organization?_revinclude=Patient:organization
GET /fhir/Observation?_include=Observation:patient&_include:iterate=Patient:link
Chaining
Filter by a property of a referenced resource:
GET /fhir/Patient?general-practitioner:Practitioner.name=Oz
GET /fhir/Practitioner?_has:Patient:general-practitioner:gender=male
- Forward chain —
<reference-param>:<target-type>.<terminal> - Reverse chain (
_has) —_has:<source-type>:<reference-param>:<terminal>
Chains of any length are supported; mix forward and reverse with . and : joins.
Example
GET /fhir/Patient?name=Smith&gender=male&_count=2&_include=Patient:general-practitioner
HTTP/1.1 200 OK
Content-Type: application/fhir+json
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "<id>",
"name": [{"family": "Smith", "given": ["John"]}],
"gender": "male"
},
"search": {"mode": "match"}
}
],
"link": [
{"relation": "self", "url": "/fhir/Patient?name=Smith&gender=male&_count=2&_page=1"}
]
}
Transaction and batch
POST /fhir/
Submit a Bundle of type: transaction (all-or-nothing) or type: batch (per-entry, independent failures). Each entry.request carries an HTTP method and URL — the server applies the entries and returns a Bundle of type: transaction-response / batch-response with the per-entry outcomes and resolved references.
Validate
Dry-run validation against the FHIR core schema and any profile the resource claims via meta.profile. Always returns 200 OK; the outcome is in OperationOutcome.id — allok or validationfail.
POST /fhir/<resourceType>/$validate
POST /fhir/<resourceType>/<id>/$validate
The same validator runs on every Create / Update / Patch — when the resource carries a meta.profile from one of the loaded Implementation Guides, a failed write returns 422. See $validate in the Aidbox docs for modes, profile parameter, and async validation.
Errors
| Status | Cause |
|---|---|
| 400 | Body parse failure or malformed search expression |
| 401 | Missing or invalid token |
| 403 | OAuth client not authorized to perform the interaction on this resource type |
| 404 | Resource type unknown, or instance not found |
| 409 | id already exists (create only) |
| 410 | Resource was deleted |
| 412 | Conditional precondition failed — If-Match mismatch, conditional create / update / delete with multiple matches |
| 422 | FHIR-profile or business-rule validation failure |
| 500 | Server-side error |
All 4xx and 5xx responses carry an OperationOutcome body describing the failure.
For full semantic detail — additional modifiers, advanced _filter operators, custom SearchParameter registration, AidboxQuery / Search resource / dot-expressions as alternatives to FHIR Search — see the Aidbox REST API docs. For Payerbox-specific operations layered on top of this base API, see the Operations index.