# Submit a reading for a patient id

Canonical page: https://docs.ovok.com/api/signals-api---observations/submit-a-reading-for-a-patient-id

`POST /v1/signals/api/observation/{patientId}`

`subject` is ignored: the path names the patient.

The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) or `dataAbsentReason`, and a combined device sample's vitals as `component[]` (each `code`, `value[x]`, optional `dataAbsentReason`). The clinical time is `effectiveDateTime`, `effectiveInstant` or `effectivePeriod.start`, else `issued`, else the server's receipt; `device` is `{reference, display}`; the first `identifier`'s `value` is stored as v1's `refId`. Other FHIR elements (`meta`, `extension`, `performer`, `interpretation`, `referenceRange`, …) are accepted and ignored. v1's `observedAt` (it wins over `effective[x]`), `refId`, string `dataAbsentReason` and `device.id` still work. A node with `dataAbsentReason` and no value is judged as carrying no value, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`.

**History is never judged.** A reading whose `category` has a coding the tenant names as history (its `historyCategories`; the default pack's: `batch-data`, `statistics`, `presence-batch`, `derived`) is stored and deduplicated like any other, answered with no alerts, and never judged nor read by the rules.

The caller must be one of the patient's submitters, admins included: a known patient it is not attached to is a 403, an unknown one a 404 with type `PATIENT_NOT_FOUND`.

**Idempotent.** A reading is named by its first `identifier` (`system|value`), else by its clinical time, code, device and `refId`. A resend of one stored within the last hour is a 409 `DUPLICATE_READING` whose `data.observationId` is the stored reading, and is neither stored nor evaluated again. A reading with neither an identifier nor a clinical time is not deduplicated.

Concurrent requests are batched server-side. With `Prefer: respond-sync` evaluation finishes first and the reply adds `alertIds`, `alerts` (`[{id, code}]`), `resolvedAlertIds` and `ruleCoverage`; without it `mode` is `async` and alerts are delivered by webhook.

### Through ovok

- **Who can call it:** Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_observations:create`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Each reading counts its patient as monitored for billing.