# FHIR API

Canonical page: https://docs.ovok.com/api/fhir

Ovok's FHIR R4 API is your door to the FHIR store behind Ovok. It speaks standard FHIR R4: resources, search, history, batches and transactions. On top of that, Ovok adds a few operations of its own.

- **Every request is scoped to your project.** The bearer token names one project, and every read, search and write runs inside it. The token's project decides this, never a `meta.project` in the request body.
- **Ovok is the only door.** There is no direct access to the FHIR store. Every FHIR call goes through the Ovok API, which applies project scoping and serves Ovok's own operations.

Only FHIR R4 is available.

## Base URL [#base-url]

| Purpose | URL |
| --- | --- |
| FHIR R4 base | `https://api.sandbox.ovok.com/fhir/R4` |
| Default version | `https://api.sandbox.ovok.com/fhir`, which serves R4 |
| CapabilityStatement | `https://api.sandbox.ovok.com/fhir/R4/metadata` |

Resource paths follow the FHIR pattern: `/fhir/R4/Patient`, `/fhir/R4/Patient/[id]`, `/fhir/R4/Observation?patient=Patient/[id]`. Use `Content-Type: application/fhir+json` for request bodies.

`GET /fhir/R4/metadata` needs no token. It returns Ovok's own CapabilityStatement: every resource and search parameter the store supports, plus the operations Ovok adds (`$lastn`, `$populate`, `$extract` and `$enable-signals`). Read it when a client must decide at runtime what it may call. The other unauthenticated discovery routes are listed under [Health and discovery](/primitives/health-and-discovery).

## Authentication [#authentication]

Send an access token on every call except `metadata`:

```http
Authorization: Bearer <accessToken>
```

Patients and practitioners get a token by signing in to a tenant; see [Authentication](/authentication), [Patient sign-in](/authentication/patient-login) and [Practitioner sign-in](/authentication/practitioner-login). A missing or expired token answers `401`.

What a call can read or change depends on two things:

1. **The project.** A token is issued for one project. Resources of other projects do not exist for it: a read answers `404` and a search leaves them out.
2. **The member's AccessPolicy.** The policy decides which resource types and interactions the member may use, and can narrow them with criteria, for example to one patient's own records. A call the policy does not grant is refused, and a search returns only what the policy lets the member read. A membership without a policy is not restricted by one; see [AccessPolicy model and membership](/access-policies/policy-model) and [Default policies](/access-policies/defaults).

Ovok's Signals operations check named Signals capabilities as well as Patient access; see [Signals route policies](/access-policies/signals).

## Supported interactions [#supported-interactions]

All standard FHIR R4 REST interactions are available, subject to your AccessPolicy:

| Interaction | Method | Path |
| --- | --- | --- |
| Read | `GET` | `/fhir/R4/[type]/[id]` |
| Vread | `GET` | `/fhir/R4/[type]/[id]/_history/[vid]` |
| Search | `GET` | `/fhir/R4/[type]?[parameters]` |
| Create | `POST` | `/fhir/R4/[type]` |
| Update | `PUT` | `/fhir/R4/[type]/[id]` |
| Patch | `PATCH` | `/fhir/R4/[type]/[id]`, with a JSON Patch body (`application/json-patch+json`) |
| Delete | `DELETE` | `/fhir/R4/[type]/[id]` |
| History | `GET` | `/fhir/R4/[type]/[id]/_history` |
| Batch or transaction | `POST` | `/fhir/R4`, with a `Bundle` of type `batch` or `transaction` |

A `transaction` Bundle is all or nothing when the project has the [`transaction-bundles`](/settings-and-features/features/transaction-bundles) feature, which new projects have. Without it, a transaction runs as a batch.

Each resource's structure, interaction paths and search parameters are in the [FHIR R4 resource reference](/api/fhir/r4), for example [Patient](/api/fhir/r4/patient), [Observation](/api/fhir/r4/observation) and [Device](/api/fhir/r4/device). Data types are in [FHIR Elements](/fhir-elements). New to FHIR? Start with [FHIR Basics](/fhir-basics).

## Ovok's own operations [#ovoks-own-operations]

Ovok serves these routes itself, under the same base URL and with the same token:

| Operation | Method and path | What it does |
| --- | --- | --- |
| [Last observations](/primitives/last-observations) | `GET /fhir/R4/Observation/$lastn` | The newest Observations per code, for example the last heart rate and blood pressure of a patient. |
| [Populate a Questionnaire](/primitives/populate-questionnaire) | `POST /fhir/R4/Questionnaire/[id]/$populate` | Pre-fills a QuestionnaireResponse for the signed-in patient. The caller must be a patient. |
| [Extract observations](/primitives/extract-observations) | `POST /fhir/R4/QuestionnaireResponse/[id]/$extract` | Builds Observations from a saved QuestionnaireResponse. |
| [Enable Signals](/primitives/enable-signals-patient) | `POST /fhir/R4/Patient/[id]/$enable-signals` | Enrols an existing Patient in Signals. Idempotent. |
| Create a Patient | `POST /fhir/R4/Patient` | A standard create. With `?withSignals=true`, the Patient is also enrolled in Signals, all or nothing. |
| Patient's Signals status | `GET /fhir/R4/Patient/[id]/signals` | Whether the Patient is enrolled in Signals, its reading and alert counts, and its newest alerts. Needs `signals_alerts:read`. |

## Errors [#errors]

FHIR routes answer errors as a FHIR [`OperationOutcome`](/api/fhir/r4/operation-outcome), not as Ovok Problem Details. Read its `issue` entries: `severity`, `code`, `details` and `expression`. The HTTP status carries the same meaning as on every Ovok route. See [Error responses](/errors#protocol-specific-errors) for how FHIR errors differ from the rest of the API.

## Examples [#examples]

Set `OVOK_TOKEN` to an access token and `PATIENT_ID` to a Patient in your project.

**Read** a Patient:

```bash
curl "https://api.sandbox.ovok.com/fhir/R4/Patient/${PATIENT_ID}" \
  --header "Authorization: Bearer ${OVOK_TOKEN}"
```

**Search** a patient's heart rates, newest first:

```bash
curl --get 'https://api.sandbox.ovok.com/fhir/R4/Observation' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --data-urlencode "patient=Patient/${PATIENT_ID}" \
  --data-urlencode 'code=http://loinc.org|8867-4' \
  --data-urlencode '_sort=-date' \
  --data-urlencode '_count=10'
```

The answer is a `searchset` [Bundle](/api/fhir/r4/bundle). Follow its `link` with `relation: next` for the next page.

**Create** a Patient:

```bash
curl --request POST 'https://api.sandbox.ovok.com/fhir/R4/Patient' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --header 'Content-Type: application/fhir+json' \
  --data '{
    "resourceType": "Patient",
    "name": [{ "given": ["Jane"], "family": "Example" }],
    "birthDate": "1960-04-12"
  }'
```

The answer is `201 Created` with the stored Patient, including its server-assigned `id`.

**`$lastn`**: the last heart rate and respiratory rate of a patient:

```bash
curl --get 'https://api.sandbox.ovok.com/fhir/R4/Observation/$lastn' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --data-urlencode "patient=Patient/${PATIENT_ID}" \
  --data-urlencode 'max=1' \
  --data-urlencode 'code=8867-4,9279-1'
```

## Caveats [#caveats]

- **Wearable and device Observations sit together.** A heart rate from a connected wearable uses the same LOINC code as one from a device, so `$lastn` and `Observation` searches return both. Wearable Observations carry a source tag in `meta.tag`, system `https://api.ovok.com/fhir/CodeSystem/wearables-source`. Filter with `_tag` or `_tag:not` to keep them apart; see [Keep wearable and device readings apart](/wearables/sync-and-read-data#keep-wearable-and-device-readings-apart) and [Cloud wearables](/wearables).
- **Your project's capabilities decide.** The resource reference documents the FHIR R4 standard. Read `/fhir/R4/metadata` before relying on a less common interaction or search parameter.
