# Structured lab reports (Precisa Saúde)

HTML: https://docs.precisa-saude.com.br/api/en/

## Overview

Extraction of PDF, JPEG or PNG lab reports into FHIR R4.

You send a lab report as PDF, JPEG or PNG and get back a FHIR R4 Bundle with the patient, a `DiagnosticReport` and one `Observation` per marker read. Each `Observation` carries the LOINC code when the marker has one, next to the name as the report printed it.

### How a report becomes a Bundle

The anchor step (03) decides which codes may appear in the result, and the check (05) throws out anything the model returned outside that set. A value with no matching passage on the page never reaches the Bundle.

1. **Submit**: the document and the purpose, through `POST /v1/extractions`
2. **Text**: the PDF's text layer, or OCR when there is none
3. **Anchor**: which catalog markers the page names
4. **Model**: value, unit and the passage each one came from
5. **Check**: drops codes outside the anchor, and passages the page does not contain
6. **FHIR R4**: the Bundle, with LOINC and ranges, in report order
7. **Result**: `GET /v1/extractions/{id}`, or the webhook notice

#### What runs on our side

- **Text**: the text layer, with OCR only for scanned reports
- **Anchor**: a search for the printed names, with no model involved
- **Model**: on Amazon Bedrock in sa-east-1, and the text stays in the region
- **Check**: the quoted passage is on the page, and the code was anchored
- **FHIR R4**: LOINC, reference range and per-document `confidence`

#### And what stays with your integration

- **Submit**: the file, and the purpose in `purpose`
- **Result**: poll by id, or wait for the webhook
- **Reading the Bundle**: with the FHIR library for your stack
- **Your rules**: what to do with `partial` and with low confidence, and how to route to review

### Asynchronous model

A submission returns `202` with an identifier. You get the result from `GET /v1/extractions/{id}`, and optionally through a webhook that carries only the identifier and the event name. Clinical data leaves through a single authenticated path.

### Webhook notification

When the key has a webhook, every finished job triggers a `POST` to your URL with `{"id", "state"}`, signed with HMAC-SHA256 over `{timestamp}.{body}` (the timestamp, a dot and the body) in the `X-Precisa-Signature` header, with the timestamp in `X-Precisa-Timestamp`. Answer 2xx within 5 seconds. Any other response, or none, makes the notification repeat with exponential backoff: 10 seconds later, then 20, 40, 80, 160 and 300, give or take 20%, for as long as the job's 15-minute window lasts. The attempts are recorded in our storage rather than in process memory, so the notification survives a service restart within that window.

The body and the `X-Precisa-Delivery-Id` header are the same on every attempt, and `X-Precisa-Delivery-Attempt` counts from 1. Use `X-Precisa-Delivery-Id` to discard repeats: the same delivery can arrive more than once. The notification status is in `webhook` on `GET /v1/extractions/{id}`, and `failed` there means there will be no further attempt. The notification is never the only path: the `GET` returns the same state.

### Retention

The service does not durably retain clinical data. The submitted document and the generated result are encrypted in temporary storage. The PDF is deleted when extraction finishes. The result remains available for repeated reads until `expiresAt`, set to 15 minutes after submission. `DELETE /v1/extractions/{id}` deletes the content earlier. From `expiresAt` onward, the API blocks access to the result even if physical deletion is still pending.

After `DELETE`, a JSON `GET` may still return `200` with the job metadata, without `bundle`, until `expiresAt`.

Physical deletion is scheduled for expiry and retried by periodic cleanup sweeps. Service interruptions or storage failures can delay it. There is no copy in backups, snapshots or another region. What persists is the job record, with no clinical content: identifier, tenant, declared purpose, state and timestamps.

### Safe retries

If a network failure eats the response to a submission, you cannot tell whether the job exists. Send the `Idempotency-Key` header with a fresh UUID per document and retry with the same key. Within the job's 15 minutes, the retry returns the same `202`, with the same `id` and the same `expiresAt`; it creates no job, does not count against the quota and is not billed. The repeated response carries `Idempotent-Replayed: true`. After the 15 minutes the key stops counting, and the same value creates a new job.

The same key with a different document, or different fields, gets `422` `idempotency_key_reused`. The service stores a hash of the key and a hash of the request, never the key itself or the document, and deletes both when the job expires.

### Limits

Up to 20 MB and 30 pages per document. Both numbers are also in `components.schemas.Limits`, so your code can validate before sending without parsing this sentence.

Uploads accept PDF, JPEG and static PNG. Each photo counts as one page. Images are limited to 16000000 pixels and 8192 pixels per side; JPEG EXIF orientation is respected. HEIC and animated PNG are unsupported.

File limits and quotas are admission ceilings, not guarantees of sustained throughput or completion time. Processing time depends on page count, OCR and work already in progress. Start the trial with one job at a time and review results from scanned PDFs.

### Capture quality

Prefer 300 DPI scans or sharply focused photos showing the entire page, with even lighting and no shadows, glare or messaging-app compression. Enlarging a small image cannot recover detail. The `inputQuality` assessment runs before the model and includes a versioned policy, metrics and reasons for each page. `passed` only means that measured signals meet the input floor; it does not guarantee accuracy or completeness. All OCR results still have `needsManualReview=true`.

Under `scan-quality-v1`, a page with at least ten words is rejected when it combines an effective resolution below 150 DPI, mean OCR confidence below 90 and at least 5% of words with confidence below 60. It is also rejected when the mean is below 65 and at least 35% of words have confidence below 60, regardless of DPI. A document with no recognized text is rejected. DPI alone does not cause rejection. Resolution is calculated from original pixels and the display size of the dominant PDF image, before OCR enlargement. For JPEG/PNG, it is relative to the page normalized by the service, rather than the camera EXIF DPI. When it cannot be measured, the reason is `resolution_unknown`.

A rejected page prevents extraction of the entire document, without a model call. Submission still returns `202`; the JSON query returns `200` with `state=failed`, `error.code=input_quality_too_low`, `inputQuality.action=retake_or_rescan` and the affected pages. No Bundle is produced. Retake the capture and upload the new file with another `Idempotency-Key`. Borderline cases continue with `review_required`, reasons for each page and mandatory human review. `not_assessed` indicates that no assessment was made, including documents with a text layer. This rejection does not refund the document quota reserved on submission.

### Reading the Bundle in your stack

The Bundle is FHIR R4, and the extensions it carries come from the fhir-brasil IG, each with a published `url`. There is no SDK of ours to install. The FHIR library for your language already knows how to read it.

- [Firely SDK](https://docs.fire.ly/projects/Firely-NET-SDK/en/latest/) for .NET
- [HAPI FHIR](https://hapifhir.io/) for Java
- [fhir.resources](https://pypi.org/project/fhir.resources/) for Python

## First call

The sandbox needs no key and answers with synthetic reports whose content and results never change: nine in Brazilian lab formats and five in US formats, one per format plus a second lab report (the report itself and its portal printout). The full set of 34 US reports is in the synthetic reports download, further down. Use it to write your integration, and the tests for it, before any contract exists. The "Try it" buttons in the reference below call the same sandbox.

### 1. Pick a report

List the sample reports. Each one comes with its PDF and the expected FHIR Bundle, so your integration can compare against what it gets back. The `region` field (`BR` or `US`) tells Brazilian formats from US ones, and on US reports `layout` names which of the five formats it is. For US reports, the expected Bundle is what the service returns when it reads the report without error: the value in the unit the service uses, with the printed one in the `as-printed` extension, plus the derived values (VLDL, estimated average glucose). When you submit through the sandbox, the range the catalog filled in comes in `catalogRanges`, outside the Bundle, as on `/v1`.

```bash
curl https://laudos.precisa-saude.com.br/sandbox/v1/fixtures
```

### 2. Submit

The response is `202`, with the job `id` and the moment it expires.

```bash
curl -X POST https://laudos.precisa-saude.com.br/sandbox/v1/extractions \
  -H "Content-Type: application/json" \
  -d '{"fixtureId":"hemograma"}'
```

### 3. Poll

Repeat until `state` leaves `queued` and `running`. `succeeded` and `partial` carry the Bundle; `failed` carries `error`, with a code from the error list.

```bash
curl https://laudos.precisa-saude.com.br/sandbox/v1/extractions/$JOB_ID
```

### 4. Fetch the Bundle

The FHIR Bundle on its own, in JSON or XML depending on `Accept`.

```bash
curl https://laudos.precisa-saude.com.br/sandbox/v1/extractions/$JOB_ID/bundle \
  -H "Accept: application/fhir+json"
```

### For your API client

- [OpenAPI (openapi.json)](https://docs.precisa-saude.com.br/api/openapi.json). The full specification, generated from the same code that validates requests. Import it into Postman, Insomnia or Bruno, or generate a client with NSwag (.NET) or OpenAPI Generator (Java).
- [Bruno collection (.zip)](https://docs.precisa-saude.com.br/api/downloads/precisa-lab-reports-bruno.zip). Ready-made calls, including the keyless sandbox walkthrough and a synthetic report to submit. Unzip it and open the folder in Bruno. The key goes in a secret variable, outside the files, and the `lang` environment variable picks the language of error messages.
- [Synthetic lab reports, US formats (.zip)](https://docs.precisa-saude.com.br/api/downloads/precisa-laudos-sinteticos-eua.zip). Thirty-four synthetic lab reports in five US report formats: monospaced lab report, column report, portal printout, patient-portal result page and whole-body DXA scan. Ages 18 to 85, both sexes, one to five pages, with out-of-range results, comparator values (`<1`), text results, a previous-result column, a trend table and dense pages. Each comes with the PDF, the generator ground truth and the expected result: the FHIR Bundle the service returns when it reads the report without error, with values in the unit the service uses and the printed value in `as-printed`, the derived values, and the `catalogRanges` alongside. Laboratory, patient and physician are fictitious, and every page carries the synthetic-sample watermark. Five of them, one of each format, are also in the sandbox.

### In production

Swap `/sandbox/v1` for `/v1`, send a PDF, JPEG or PNG as `multipart/form-data` with the `purpose` field, and pass the key in the `X-API-Key` header. The rest of the flow is the same.

To retry without creating a duplicate job, also send `Idempotency-Key` with a fresh UUID per document. The behavior is described under "Safe retries", in the overview.

### Camera and photo library uploads

Your web or native app should let the user take a photo and upload without a manual conversion step. Request or export JPEG automatically, apply orientation and resize only as needed to fit the published limits while keeping small text readable. iPhones can produce HEIC/HEIF; unconverted HEIC/HEIF is not accepted by this API. Native clients can request JPEG; web clients must verify the picker output and convert supported images to JPEG before submission. Test camera capture and existing-photo selection separately on iOS Safari, Android Chrome and your native apps. Do not rely solely on implicit browser conversion. Send the matching media type in the multipart file part. For unreadable text, offer a preview and retake action.

Production keys are issued by Precisa. Ask for one through the [contact form](https://precisa-saude.com.br/laudos#contato).

### Encoding

Every response is UTF-8: `charset=utf-8` in the `Content-Type` of both JSON and XML, and `encoding="UTF-8"` in the XML declaration. Exam names in Portuguese carry accents on almost every line, and a client that decodes them as Latin-1 shows `HematÃ³crito` instead of `Hematócrito`.

## Synthetic report layouts (104)

Fictional reports in Brazilian Portuguese and English, generated by lab-report-fixtures. Explore digital PDFs and simulated scans with field bounding boxes. The answer keys describe printed content, not measured extraction accuracy. Browsing makes no model calls; the sandbox below returns predefined responses. Use these samples to evaluate digital and scanned PDFs in both languages. OCR results require review against the original; the gallery does not establish extraction accuracy.

[lab-report-fixtures · MIT](https://github.com/Precisa-Saude/lab-report-fixtures)

[Full corpus manifest](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/manifest.json)

| Report | Report language | Layout | Quality | Page | Download PDF | Field annotations | Answer key |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ruled-529000-digital | en-US | ruled | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-digital.expected.json) |
| ruled-529000-clean | en-US | ruled | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-clean.expected.json) |
| ruled-529000-typical | en-US | ruled | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-typical.expected.json) |
| ruled-529000-difficult | en-US | ruled | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529000-difficult.expected.json) |
| ruled-529001-digital | en-US | ruled | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-digital.expected.json) |
| ruled-529001-clean | en-US | ruled | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-clean.expected.json) |
| ruled-529001-typical | en-US | ruled | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-typical.expected.json) |
| ruled-529001-difficult | en-US | ruled | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/ruled-529001-difficult.expected.json) |
| borderless-529010-digital | en-US | borderless | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-digital.expected.json) |
| borderless-529010-clean | en-US | borderless | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-clean.expected.json) |
| borderless-529010-typical | en-US | borderless | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-typical.expected.json) |
| borderless-529010-difficult | en-US | borderless | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529010-difficult.expected.json) |
| borderless-529011-digital | en-US | borderless | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-digital.expected.json) |
| borderless-529011-clean | en-US | borderless | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-clean.expected.json) |
| borderless-529011-typical | en-US | borderless | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-typical.expected.json) |
| borderless-529011-difficult | en-US | borderless | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/borderless-529011-difficult.expected.json) |
| wrapped-529020-digital | en-US | wrapped | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-digital.expected.json) |
| wrapped-529020-clean | en-US | wrapped | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-clean.expected.json) |
| wrapped-529020-typical | en-US | wrapped | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-typical.expected.json) |
| wrapped-529020-difficult | en-US | wrapped | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529020-difficult.expected.json) |
| wrapped-529021-digital | en-US | wrapped | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-digital.expected.json) |
| wrapped-529021-clean | en-US | wrapped | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-clean.expected.json) |
| wrapped-529021-typical | en-US | wrapped | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-typical.expected.json) |
| wrapped-529021-difficult | en-US | wrapped | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/wrapped-529021-difficult.expected.json) |
| range-below-529030-digital | en-US | range-below | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-digital.expected.json) |
| range-below-529030-clean | en-US | range-below | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-clean.expected.json) |
| range-below-529030-typical | en-US | range-below | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-typical.expected.json) |
| range-below-529030-difficult | en-US | range-below | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529030-difficult.expected.json) |
| range-below-529031-digital | en-US | range-below | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-digital.expected.json) |
| range-below-529031-clean | en-US | range-below | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-clean.expected.json) |
| range-below-529031-typical | en-US | range-below | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-typical.expected.json) |
| range-below-529031-difficult | en-US | range-below | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/range-below-529031-difficult.expected.json) |
| history-529040-digital | en-US | history | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-digital.expected.json) |
| history-529040-clean | en-US | history | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-clean.expected.json) |
| history-529040-typical | en-US | history | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-typical.expected.json) |
| history-529040-difficult | en-US | history | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529040-difficult.expected.json) |
| history-529041-digital | en-US | history | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-digital.expected.json) |
| history-529041-clean | en-US | history | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-clean.expected.json) |
| history-529041-typical | en-US | history | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-typical.expected.json) |
| history-529041-difficult | en-US | history | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/history-529041-difficult.expected.json) |
| continued-529050-digital | en-US | continued | Digital | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-digital.expected.json) |
| continued-529050-clean | en-US | continued | Scan · 300 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-clean.expected.json) |
| continued-529050-typical | en-US | continued | Scan · 170 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-typical.expected.json) |
| continued-529050-difficult | en-US | continued | Scan · 110 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529050-difficult.expected.json) |
| continued-529051-digital | en-US | continued | Digital | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-digital.expected.json) |
| continued-529051-clean | en-US | continued | Scan · 300 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-clean.expected.json) |
| continued-529051-typical | en-US | continued | Scan · 170 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-typical.expected.json) |
| continued-529051-difficult | en-US | continued | Scan · 110 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529051-difficult.expected.json) |
| br-ruled-529000-digital | pt-BR | ruled | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-digital.expected.json) |
| br-ruled-529000-clean | pt-BR | ruled | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-clean.expected.json) |
| br-ruled-529000-typical | pt-BR | ruled | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-typical.expected.json) |
| br-ruled-529000-difficult | pt-BR | ruled | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529000-difficult.expected.json) |
| br-ruled-529001-digital | pt-BR | ruled | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-digital.expected.json) |
| br-ruled-529001-clean | pt-BR | ruled | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-clean.expected.json) |
| br-ruled-529001-typical | pt-BR | ruled | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-typical.expected.json) |
| br-ruled-529001-difficult | pt-BR | ruled | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-ruled-529001-difficult.expected.json) |
| br-borderless-529010-digital | pt-BR | borderless | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-digital.expected.json) |
| br-borderless-529010-clean | pt-BR | borderless | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-clean.expected.json) |
| br-borderless-529010-typical | pt-BR | borderless | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-typical.expected.json) |
| br-borderless-529010-difficult | pt-BR | borderless | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529010-difficult.expected.json) |
| br-borderless-529011-digital | pt-BR | borderless | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-digital.expected.json) |
| br-borderless-529011-clean | pt-BR | borderless | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-clean.expected.json) |
| br-borderless-529011-typical | pt-BR | borderless | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-typical.expected.json) |
| br-borderless-529011-difficult | pt-BR | borderless | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-borderless-529011-difficult.expected.json) |
| br-wrapped-529020-digital | pt-BR | wrapped | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-digital.expected.json) |
| br-wrapped-529020-clean | pt-BR | wrapped | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-clean.expected.json) |
| br-wrapped-529020-typical | pt-BR | wrapped | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-typical.expected.json) |
| br-wrapped-529020-difficult | pt-BR | wrapped | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529020-difficult.expected.json) |
| br-wrapped-529021-digital | pt-BR | wrapped | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-digital.expected.json) |
| br-wrapped-529021-clean | pt-BR | wrapped | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-clean.expected.json) |
| br-wrapped-529021-typical | pt-BR | wrapped | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-typical.expected.json) |
| br-wrapped-529021-difficult | pt-BR | wrapped | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-wrapped-529021-difficult.expected.json) |
| br-range-below-529030-digital | pt-BR | range-below | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-digital.expected.json) |
| br-range-below-529030-clean | pt-BR | range-below | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-clean.expected.json) |
| br-range-below-529030-typical | pt-BR | range-below | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-typical.expected.json) |
| br-range-below-529030-difficult | pt-BR | range-below | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529030-difficult.expected.json) |
| br-range-below-529031-digital | pt-BR | range-below | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-digital.expected.json) |
| br-range-below-529031-clean | pt-BR | range-below | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-clean.expected.json) |
| br-range-below-529031-typical | pt-BR | range-below | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-typical.expected.json) |
| br-range-below-529031-difficult | pt-BR | range-below | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-range-below-529031-difficult.expected.json) |
| br-history-529040-digital | pt-BR | history | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-digital.expected.json) |
| br-history-529040-clean | pt-BR | history | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-clean.expected.json) |
| br-history-529040-typical | pt-BR | history | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-typical.expected.json) |
| br-history-529040-difficult | pt-BR | history | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529040-difficult.expected.json) |
| br-history-529041-digital | pt-BR | history | Digital | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-digital.expected.json) |
| br-history-529041-clean | pt-BR | history | Scan · 300 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-clean.expected.json) |
| br-history-529041-typical | pt-BR | history | Scan · 170 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-typical.expected.json) |
| br-history-529041-difficult | pt-BR | history | Scan · 110 DPI | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-history-529041-difficult.expected.json) |
| br-continued-529050-digital | pt-BR | continued | Digital | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-digital.expected.json) |
| br-continued-529050-clean | pt-BR | continued | Scan · 300 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-clean.expected.json) |
| br-continued-529050-typical | pt-BR | continued | Scan · 170 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-typical.expected.json) |
| br-continued-529050-difficult | pt-BR | continued | Scan · 110 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529050-difficult.expected.json) |
| br-continued-529051-digital | pt-BR | continued | Digital | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-digital.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-digital.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-digital.expected.json) |
| br-continued-529051-clean | pt-BR | continued | Scan · 300 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-clean.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-clean.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-clean.expected.json) |
| br-continued-529051-typical | pt-BR | continued | Scan · 170 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-typical.expected.json) |
| br-continued-529051-difficult | pt-BR | continued | Scan · 110 DPI | 2 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-difficult.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-difficult.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529051-difficult.expected.json) |
| continued-529800-mixed | en-US | continued | Digital + scan | 6 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529800-mixed.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529800-mixed.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529800-mixed.expected.json) |
| continued-529815-typical | en-US | continued | Scan · 170 DPI | 15 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529815-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529815-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529815-typical.expected.json) |
| continued-529830-typical | en-US | continued | Scan · 170 DPI | 30 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529830-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529830-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/continued-529830-typical.expected.json) |
| unreadable-529999-unreadable | en-US | borderless | Unreadable | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/unreadable-529999-unreadable.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/unreadable-529999-unreadable.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/unreadable-529999-unreadable.expected.json) |
| br-continued-529800-mixed | pt-BR | continued | Digital + scan | 6 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529800-mixed.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529800-mixed.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529800-mixed.expected.json) |
| br-continued-529815-typical | pt-BR | continued | Scan · 170 DPI | 15 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529815-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529815-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529815-typical.expected.json) |
| br-continued-529830-typical | pt-BR | continued | Scan · 170 DPI | 30 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529830-typical.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529830-typical.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-continued-529830-typical.expected.json) |
| br-unreadable-529999-unreadable | pt-BR | borderless | Unreadable | 1 | [PDF](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-unreadable-529999-unreadable.pdf) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-unreadable-529999-unreadable.fields.json) | [JSON](https://docs.precisa-saude.com.br/api/downloads/lab-report-fixtures/br-unreadable-529999-unreadable.expected.json) |

## Generate your client

Precisa does not publish an SDK. The contract is the OpenAPI document, and the client for your language comes out of it, with the generator your team already uses and at the contract version you downloaded. The commands below are the ones our CI runs on every contract change: it generates the .NET and Java clients, checks the name of each operation and builds both.

Download the contract into the folder where the client will be generated:

```bash
curl -O https://docs.precisa-saude.com.br/api/openapi.json
```

### .NET: NSwag 14.7.1

Generates a single class, `PrecisaLaudosClient`, that uses `System.Text.Json` and builds in a .NET 8 project with no extra package.

```bash
dotnet tool install --global NSwag.ConsoleCore --version 14.7.1
nswag openapi2csclient /input:openapi.json /output:PrecisaLaudosClient.cs \
  /namespace:Precisa.Laudos /className:PrecisaLaudosClient /jsonLibrary:SystemTextJson
```

### Java: OpenAPI Generator 7.25.0

Generates a Maven project, with an `ExtractionsApi` class and one model per schema in the contract. CI builds it with `mvn package`.

```bash
docker run --rm -v "$PWD:/local" openapitools/openapi-generator-cli:v7.25.0 generate \
  -i /local/openapi.json -g java -o /local/precisa-laudos-client \
  --group-id br.com.precisasaude --artifact-id precisa-laudos-client \
  --api-package br.com.precisasaude.laudos.api \
  --model-package br.com.precisasaude.laudos.model \
  --invoker-package br.com.precisasaude.laudos.client
```

The names your code will use come from the contract:

- Operations: `getHealth`, `createExtraction`, `getExtraction`, `deleteExtraction`, `getExtractionBundle`
- Models: `CatalogRange`, `Confidence`, `Error`, `ErrorCode`, `ErrorDetail`, `Extension`, `ExtractionAccepted`, `ExtractionDeleted`, `ExtractionResult`, `Health`, `JobState`, `Limits`, `Progress`, `Purpose`, `SkippedBiomarker`, `SkippedPage`, `Versions`, `WebhookDelivery`

The Bundle comes out as a free-form object in the generated client (`Object` in Java, `object` in .NET), because its shape is FHIR R4. Read it with the FHIR library for your language, listed in the overview.

The document is OpenAPI 3.0.3. Before generating the clients, CI checks it with OpenAPI Generator's validator.

## Where each value comes from

Each `Observation` says where its value sits in the document and how far you can trust it. Your team can check a value against the PDF without hunting page by page, and send what deserves a human look to review.

The extensions live in `Observation.extension`, and each one's `url` is the prefix below followed by its name. Extensions with parts carry them in a nested `extension`, with the part name in the `url`.

```
https://fhir-brasil.dev.br/ig/StructureDefinition/
```

- `extraction-source`: The pages where the test appears, starting with the page of the quoted passage, and the passage as the document prints it. On scanned documents it also gives the passage's box on the page, as fractions of the page (0 to 1, origin at the top left), so you can highlight the value in the PDF. Calculated values do not have it, since they are not printed anywhere.
- `extraction-confidence`: `reading` says whether the characters were read correctly: 1 when the document has a text layer, and the OCR confidence in the passage's numbers when it is scanned. `interpretation` says whether the value was assigned to the right test, unit and range. They fail for different reasons, which is why they never collapse into a single number.
- `as-printed`: The value and range as the document prints them, when the `Observation` carries the value converted to another unit. Only present when a conversion happened.
- `derived-from-ocr`: The value came from a scanned document. It is there so you can apply whatever threshold you like to that data.
- `superseded`: The value is printed, but the report itself replaces it with another one. Today this covers the glomerular filtration rate from a race-based equation, printed next to the race-free one. Show the other value.

### In standard R4 fields

- `derivedFrom`: The value was calculated rather than read: VLDL from triglycerides, estimated average glucose from glycated hemoglobin. It points to the observations it came from. A calculated value has no `extraction-source`, because there is no passage to quote.
- `valueQuantity.comparator`: The report prints a limit instead of a measurement: `<` for "inferior a 0,5" (below 0.5), `>` for "superior a 90" (above 90).
- `referenceRange`: The range the report prints, and nothing else. A range with a single limit ("inferior a 190", below 190) comes out one-sided. A qualitative reference confirmed on the page, such as "NEGATIVE", goes in `referenceRange[].text` with its printed spelling. When the report prints no range at all the field is absent, and the Precisa catalog range for that test goes in `catalogRanges`, in the envelope.
- `interpretation`: `H` and `L` for out of range, and `HH` and `LL` when the report flags the value as critical. Without a printed range, the comparison is against the catalog range in `catalogRanges`.

### In the envelope

The `GET` summarizes the document so you can decide without walking the Bundle. `confidence` carries the average and the minimum of both axes. Watch the minimum, because eighteen good values and one illegible one still average out high. `needsManualReview` is the extraction raising its own hand (an OCR-derived document, a page that failed, a test left out, a low-confidence value), and where the report goes next is still your decision.

`inputQuality` assesses the capture before the model, with reasons and metrics for each page. `input_quality_too_low` means a new capture is needed, in focus and with even lighting, preferably at 300 DPI. The job becomes `failed`, without a Bundle, and `inputQuality.action` is `retake_or_rescan`. Upload the new file with another `Idempotency-Key`; rejection does not refund the quota reserved on submission. `review_required` calls for a check. `passed` does not guarantee accuracy or completeness; all OCR still requires human review.

`failedPages` lists the pages the model could not read, which are what make a job `partial`, and `skippedPages` lists every page that produced no result, each with its reason. `skippedBiomarkers` has the tests the report printed that did not become an `Observation`, also with a reason. `catalogRanges` gives the Precisa catalog range for each `Observation` whose report printed no range, keyed by its `id`. It stays out of `referenceRange` because the lab did not publish it. `degradedOcr` means the document is scanned and the reading came out too thin to trust without a check. While the job runs, `progress` reports the phase and, during reading, how many pages are done.

## Extensions

Not every contract wants to pay for every step, so some stay off until you ask for them. Turn them on at submission, either by repeating the `extensions` field or by sending a comma-separated list. A name outside the list rejects the submission with `400` instead of being ignored.

Precisa maintains the extensions, and your code does not run inside the service. If you need logic of your own, build it on top of the Bundle.

**Submission with extensions**

```bash
curl -X POST https://laudos.precisa-saude.com.br/v1/extractions \
  -H "X-API-Key: $PRECISA_API_KEY" \
  -F purpose=care_coordination \
  -F extensions=history \
  -F extensions=lab-name-vision \
  -F heightCm=175 \
  -F file=@report.pdf
```

- `document-patient`: Returns the patient name in `documentPatient`, as the document prints it, so you can check that the report belongs to who you think it does. With it off, the service returns nothing about the patient that you did not send. The name goes away with the Bundle and is not kept in the job record. No model cost, since the name already comes out of reading the report.
- `history`: Reads trend tables (the same measurement on earlier dates) and returns one `DiagnosticReport` per date, with the `reprinted-in` extension pointing to the report. The report's own date is not repeated, because it fills in the main report. Costs one model call per page with a table.
- `lab-name-vision`: When the report text does not include the lab name (on some reports it only appears as a logo), reads the first page as an image. Does not run on degraded scans. Costs one vision call, and only on documents that arrive without a name.

### Patient height

Send `heightCm`, in centimeters, when you have it. Some calculated markers depend on it, such as BMI and the muscle mass index. The height applies to that extraction only and is not kept in the job record.

## Data handling

You can send this section on its own to your security team and your data protection officer. The direct link is `docs.precisa-saude.com.br/api/en/#tratamento-de-dados`.

### Retention

The service does not durably retain clinical data. The submitted document and the generated result are encrypted in temporary storage. The PDF is deleted when extraction finishes. The result remains available for repeated reads until `expiresAt`, set to 15 minutes after submission. `DELETE /v1/extractions/{id}` deletes the content earlier. From `expiresAt` onward, the API blocks access to the result even if physical deletion is still pending.

After `DELETE`, a JSON `GET` may still return `200` with the job metadata, without `bundle`, until `expiresAt`.

Physical deletion is scheduled for expiry and retried by periodic cleanup sweeps. Service interruptions or storage failures can delay it. There is no copy in backups, snapshots or another region. What persists is the job record, with no clinical content: identifier, tenant, declared purpose, state and timestamps.

### Where data is processed

On AWS in São Paulo (`sa-east-1`): the service, the temporary storage and the model that performs the extraction. The document does not leave the region to be processed.

### Encryption at rest

Document and result are stored in S3, encrypted with AES-256. The job record lives in DynamoDB, encrypted with KMS.

### No copies

The tables have no point-in-time recovery and no replica in another region, the bucket has no replication, and none of it is part of a backup plan.

### Purpose

Every submission declares a `purpose` from a closed list: `care_coordination`, `member_self_service`, `clinical_quality`, `research`. A value outside the list is rejected with `400`, and the purpose is stored in the job record.

### Logs

The service logs counts, codes and durations, never report content. Logs are kept for 30 days.

### Role under the LGPD

Under the LGPD, Brazil's general data protection law, the partner is the data controller. Precisa handles the document as processor (operadora, in the law's terms), for the purpose the submission declares.

### Administrative data

The service returns the clinical content of the report, and the administrative part stays with you: the patient's CPF or CNS (Brazil's taxpayer and national health card numbers), the CNES (national registry number) of the performing lab and the `performer` reference. You hold that data, since you authorized the test. The Bundle's `Patient` carries a generic name and an `id`, which is yours when you send the `x-subject-id` header (letters, digits, dot and hyphen, up to 64 characters), and the `DiagnosticReport`'s `performer` carries the lab name as the report prints it, with no CNES. The service does not return a CPF or CNS extracted from the PDF. With the `document-patient` extension, the name printed on the report is returned in `documentPatient` for verification and deleted with the result.

## Errors

Every response outside `2xx` has the same body. Branch on `code`, which comes from a closed list. The `message` is written for people and may change.

Some errors only show up after the `202`, because extraction runs once the submission has been accepted. Those arrive in the `error` field of the `GET`, with the job in `failed`.

The `message` comes in Portuguese, or in English with `Accept-Language: en`. Without the header, the language set in your key's contract applies. The `code` is the same in both, and the response states its language in `Content-Language`.

**Error body**

```json
{
  "error": {
    "code": "invalid_input",
    "message": "Send the document in the `file` field, as multipart."
  }
}
```

- `document_cap_reached`: `403` on submission. The key has used every document under its contract cap, such as the one for an evaluation period. Retrying will not help: only Precisa can raise the cap, on the same key. No job was created and nothing was counted.
  Message: This key has used all N documents of the evaluation period. To continue, write to contato@precisa-saude.com.br and the limit is raised on the same key.
- `expired`: `410`, or in `error` on the `GET`. The 15-minute window has passed, or the result was deleted with `DELETE`. Final: the only way back is to submit the document again.
  Message: The result expired and no longer exists.
- `file_too_large`: `413` on submission. The document exceeds `maxBytes`, or the image exceeds `maxImagePixels` or `maxImageSide`, before the job is queued.
  Message: The document exceeds 20971520 bytes.
- `idempotency_key_reused`: `422` on submission. The `Idempotency-Key` was already used within the 15 minutes with a different document or different fields. The earlier job is not returned, so generate a new key for the new submission.
  Message: This `Idempotency-Key` was already used with a different document or different fields. Use a new key.
- `image_only_pdf`: In `error` on the `GET`, or `422` when the Bundle is requested (the `/bundle` route, or a `GET` with an XML `Accept`). The PDF has no text layer and OCR could not read it. Retrying the same file fails the same way.
  Message: The PDF has no text layer and OCR could not read it.
- `input_quality_too_low`: In `error` on `GET`, or `422` when requesting the Bundle. Capture quality falls below the input policy. Check `inputQuality.pages` and upload a new capture with a new Idempotency-Key. No model call was made and no Bundle was produced.
  Message: The capture quality is insufficient for extraction. Check inputQuality.pages and retake the photo or rescan in focus, without shadows or compression, preferably at 300 DPI. Upload the new file with a new Idempotency-Key.
- `invalid_input`: `400` on submission. Malformed request, missing required field or `purpose` not in the list. Also the `404` for a job that does not exist, and the `406` for an `Accept` or `_format` that no served type matches. Fix it before retrying.
  Message: Send the document in the `file` field, as multipart.
- `missing_collection_date`: In `error` on the `GET`, or `422` when the Bundle is requested. The report was read but has no collection date, which the Bundle requires. Retrying as is will not help. Resubmit with `collectionDate`, which only applies to reports without a date.
  Message: The report has no collection date, and the Bundle requires one.
- `not_a_lab_report`: In `error` on the `GET`, or `422` when the Bundle is requested. The document was read and does not look like a lab report.
  Message: The document does not look like a lab report.
- `not_ready`: `409` when the Bundle is requested (the `/bundle` route, or a `GET` with an XML `Accept`) while extraction is still running, or on submission while an earlier submission with the same `Idempotency-Key` is still being recorded. Come back after `Retry-After`. Unlike `expired`, this one clears on its own.
  Message: Extraction is still running. Try again shortly.
- `processing_error`: In `error` on the `GET` (or `422` when the Bundle is requested), or `500` and `503` when the request itself fails (the `503` includes `Retry-After`). A failure on our side, including a document that looks like a lab report but yielded no markers. When the model read no page even after retrying, the job carries `failedPages`, and resubmitting fixes it.
  Message: Failed to process the document.
- `rate_limited`: `429` on submission. The tenant's quota or concurrency was exceeded. Includes `Retry-After` in seconds.
  Message: Requests per minute are at the contract limit.
- `too_many_pages`: `413` on submission. The document exceeds `maxPages`, checked before the job is queued.
  Message: The document has 31 pages, and the limit is 30.
- `unauthorized`: `401`. Key missing, unknown or revoked. The problem is the credential, not the request body.
  Message: Invalid or revoked key.
- `unsupported_media_type`: `415` on submission. Send a valid PDF, JPEG or static PNG with its matching declared media type.
  Message: Send `application/pdf`, `image/jpeg` or `image/png`.

## Versions and changes

The contract version is in the path, `/v1`. Every response from the job `GET` carries `versions`, which says what produced that result:

- `api`: Contract version, such as `v1`.
- `catalog`: Version of the Precisa catalog (the `@precisa-saude/fhir` package) that supplied the codes, the ranges and the LOINC mapping, for example `0.41.2`.
- `loinc`: LOINC release the catalog was checked against, for example `2.83`. `unknown` when the catalog does not declare one.
- `model`: Model that produced the extraction.
- `prompt`: Version of the extraction prompt.

### Changes

Changes to the `/v1` contract, newest first.

- 2026-10-09: An accepted job whose service instance stops mid-extraction is resumed by another instance from the stored document, so it still ends in a result. When too little of the window is left to resume, it fails with `processing_error` and a message saying to submit again, where it stayed `running` until it expired. While an instance shuts down, `GET /health` and `POST /v1/extractions` return `503` with `Retry-After`.
- 2026-10-09: `DELETE /v1/extractions/{id}` also stops a running extraction within 5 seconds: after that no new model call starts, and the job ends `failed` with `expired`, as it does when the 15 minutes run out mid-processing. A resubmission with an `Idempotency-Key` that is already held has its own per-minute and concurrency limits, the same numbers as the contract and separate from submissions; above them it gets `429 rate_limited` with `Retry-After`. A resubmission whose body size cannot be the original document gets `422 idempotency_key_reused` before the upload finishes.
- 2026-10-09: In `versions`, `catalog` carries the version of the catalog that produced the result (`0.41.2` today) and `loinc` the LOINC release it was checked against (`2.83` today), where both said `local`.
- 2026-10-09: A webhook notification that does not get a 2xx within 5 seconds is retried with exponential backoff (10 seconds, then 20, 40, 80, 160 and 300, give or take 20%) for as long as the job's window lasts, and survives a service restart. Every attempt carries `X-Precisa-Delivery-Id`, the same across attempts, and `X-Precisa-Delivery-Attempt`. `GET /v1/extractions/{id}` returns the notification status in `webhook`.
- 2026-10-09: A text result with a verified LOINC answer ("Negative", "Non-reactive", "2+") comes in `valueCodeableConcept`, with the LOINC and SNOMED CT codes and the printed text in `text`; text without one stays in `valueString`. A job in which the model read no page, even after the retries, fails with `processing_error`, lists the pages in `failedPages` and says to resubmit, where it failed as a document with no markers. New codes for regional bone density on whole-body DXA (`BMD_Arms` to `BMD_Trunk`, seven regions, without LOINC), and on a body-composition report the catalog range follows the printed sex and age. Units lose the "(calc)" mark, "% of total Hgb" becomes `%`, and "Thousand/uL" and "Million/uL" get their UCUM code. In the sandbox, the expected result of the US reports is what the service returns, and the job envelope carries `catalogRanges`.
- 2026-10-09: `GET /health` returns `503` with `{"status":"unavailable"}` when the service cannot serve `/v1`. On `POST /v1/extractions`, the per-minute quota, concurrency and document cap are checked before the file is read, and a request rejected later by the form also counts toward the per-minute quota. The sandbox limits each address to 300 requests per minute and 200 open jobs, answering `429 rate_limited` with `Retry-After`.
- 2026-10-09: The contract is published as OpenAPI 3.0.3 instead of 3.1. Single-value constants become a one-item `enum`, and a description next to a `$ref` is dropped, since 3.0 ignores it. Requests and responses do not change.
- 2026-10-09: The contract declares `GET /health`, which needs no key and returns `{"status":"ok"}`.
- 2026-10-09: The contract declares the optional `x-subject-id` header on `POST /v1/extractions`, with `maxLength` and `pattern`. A value that does not match is accepted and ignored, and the job id is used as the `Patient` id.
- 2026-10-09: Retrieving a job or its Bundle after `DELETE` or after the deadline returns `410 expired`, as documented, where it could return `503 processing_error`.
- 2026-10-09: Document cap per key, for evaluation periods: a submission over the cap returns `403` with the new `document_cap_reached` code.
- 2026-10-09: Each operation has an `operationId` (`createExtraction`, `getExtraction`, `deleteExtraction`, `getExtractionBundle`) and the `Extractions` tag, and repeated schemas became named components, so generated clients get stable names. Requests and responses do not change.
- 2026-10-09: `GET /v1/extractions/{id}` returns the Bundle in XML with `Accept: application/fhir+xml` or `application/xml`, and the R4 `_format` parameter is honored, ahead of `Accept`. Asking for the Bundle of a failed job returns `422` with the failure code, where it used to return `410`.
- 2026-10-09: Error `message` in Portuguese or English, chosen by `Accept-Language` or by the language of the key's contract, with `Content-Language` on the response; the `code` does not change. The contract declares `Accept-Language`, `Content-Language` and each code's default message in `x-error-messages`.
- 2026-10-09: `Idempotency-Key` header on submission, a new `idempotency_key_reused` code (`422`), and `409 not_ready` on a repeated submission while the first one is still being recorded.
- 2026-10-06: `skippedBiomarkers` gains the `specimen-mismatch` reason and the `detail` field.
- 2026-10-06: `catalogRanges` in the envelope. The Precisa catalog range leaves the Bundle's `referenceRange`, which now carries only the range the report prints.
- 2026-09-29: `collectionDate` field on submission, the fallback collection date for reports that print none.
- 2026-09-28: The `document-patient` extension and the `documentPatient` field, plus `degradedOcr`. `skippedPages` changes from a list of page numbers to a list of objects with `pageNumber` and `reason`, an incompatible change.
- 2026-09-28: `extensions` field on submission (`history`, `lab-name-vision`), and `heightCm`.
- 2026-09-28: Provenance and two-axis confidence on each `Observation`. In the envelope, `progress`, `failedPages`, `needsManualReview`, `resultDeliveredAt` and `skippedBiomarkers`; `confidence.reading` becomes optional.
- 2026-09-23: The `Idempotency-Key` header is removed from the contract, because nothing in the service read it yet.
- 2026-09-23: `/v1` goes live: submission with the key in `X-API-Key`, size and page limits, retrieval, the Bundle route and deletion.

## Reference

The reference is generated from the API contract, which is written in Portuguese, and what you read here is our English translation of it. When the contract changes before the translation does, the changed sentence shows up in Portuguese until the translation catches up.

### Check that the service is ready

`GET /health`

Returns `200` with `{"status":"ok"}` when the service is ready to serve `/v1`, and `503` with `{"status":"unavailable"}` when it is not. It needs no key and says nothing else: version and dependency state are left out on purpose, so the route gives nothing away to scanners. Use it to check network, DNS and TLS before the first submission; to check the key, call `/v1`, which returns `401` when the key is not valid.

- `200`: The service is ready to serve requests.
- `503`: The service is up but cannot serve `/v1`.

**Example**

```bash
curl https://laudos.precisa-saude.com.br/health
```

### Submit a report for extraction

`POST /v1/extractions`

Submits a document. Responds 202 with the job identifier, and the result comes from the GET.

- `202`: Job accepted. When the `Idempotency-Key` was already used for the same request within the window, this is the first submission's `202` repeated, with no new job. (`Idempotent-Replayed`)
- `400`: Invalid input: password-protected PDF, `purpose` not in the list, unknown extension, `heightCm` out of range, `collectionDate` in the wrong format, or an `Idempotency-Key` that is empty, too long or not ASCII. `invalid_input`. (`Content-Language`)
- `403`: The key has used every document under its contract cap, such as the one for an evaluation period. Checked after validation and quota, and only on keys with a cap. No `Retry-After`, because retrying will not help. `document_cap_reached`. (`Content-Language`)
- `409`: Two simultaneous submissions with the same `Idempotency-Key`, and the first is still being recorded. Includes `Retry-After`; a retry after that gets the first submission's `202`. `not_ready`. (`Retry-After`, `Content-Language`)
- `413`: Document over `maxBytes` or `maxPages`, both checked before the job is queued. `file_too_large` or `too_many_pages`. (`Content-Language`)
- `415`: Invalid format or a format other than PDF, JPEG and static PNG. `unsupported_media_type`. (`Content-Language`)
- `422`: The `Idempotency-Key` was already used within the window with a different document or different fields. The earlier job is not returned. `idempotency_key_reused`. (`Content-Language`)
- `429`: Quota or concurrency exceeded, both checked before the file is read. Includes `Retry-After` in seconds. `rate_limited`. (`Retry-After`, `Content-Language`)

**Example**

```bash
curl -X POST https://laudos.precisa-saude.com.br/v1/extractions \
  -H "X-API-Key: $PRECISA_API_KEY" \
  -F "purpose=care_coordination" \
  -F "file=@report.pdf;type=application/pdf"
```

### Retrieve a job

`GET /v1/extractions/{id}`

The job state, with the Bundle embedded in `bundle` when there is one. With an XML `Accept` (`application/fhir+xml` or `application/xml`), or an XML `_format`, the response is the Bundle itself in XML, the same as `/v1/extractions/{id}/bundle`, because the envelope is our own JSON and has no XML form. In that case a job still running gets `409` with `Retry-After`, and a failed job gets `422` with the failure code, just like the Bundle route.

- `200`: In JSON, the job state, with the Bundle when there is one. Extraction happens **after** the `202`, so all of its errors arrive here, in `error`, and never as a status on the `POST`: `image_only_pdf`, `input_quality_too_low`, `not_a_lab_report`, `missing_collection_date` and `processing_error`. The `partial` state carries a Bundle and flags pages that were not read. In XML, the FHIR R4 Bundle on its own. (`Content-Language`)
- `404`: Does not exist, or belongs to another tenant. The response is the same in both cases. (`Content-Language`)
- `406`: The `Accept` header, or `_format`, matches none of the served types. (`Content-Language`)
- `409`: XML `Accept` only: extraction is still running, and the state envelope has no XML form. Includes `Retry-After`. `not_ready`. In JSON the same job returns `200` with `state` `queued` or `running`. (`Retry-After`, `Content-Language`)
- `410`: Expired. The result stopped existing after the 15 minutes. `expired`. (`Content-Language`)
- `422`: Extraction failed and there is no Bundle. The code is the same one the envelope would carry in `error`: `image_only_pdf`, `input_quality_too_low`, `not_a_lab_report`, `missing_collection_date` or `processing_error`. Final for this job. (`Content-Language`)

**Example**

```bash
curl https://laudos.precisa-saude.com.br/v1/extractions/$JOB_ID \
  -H "X-API-Key: $PRECISA_API_KEY"
```

### Delete a result early

`DELETE /v1/extractions/{id}`

Deletes the document and the result before the deadline and stops a running extraction within 5 seconds: after that no new model call starts, and the job ends `failed` with `expired`. Reading a result does not consume it, so this is how you end a job right away instead of waiting out the 15 minutes. Idempotent.

- `200`: Deleted, or already gone. The body carries the final state.
- `404`: Does not exist, or belongs to another tenant. The response is the same in both cases. (`Content-Language`)

**Example**

```bash
curl -X DELETE https://laudos.precisa-saude.com.br/v1/extractions/$JOB_ID \
  -H "X-API-Key: $PRECISA_API_KEY"
```

### Fetch the FHIR Bundle

`GET /v1/extractions/{id}/bundle`

The FHIR R4 Bundle on its own. `Accept` picks the type, and the default is `application/fhir+json`. `application/json` and `application/xml` are accepted as aliases, and the response comes back labeled with the type you asked for, not its `fhir+` equivalent. The R4 `_format` parameter wins over `Accept` when present. A type outside the list gets a 406 instead of JSON in silence.

- `200`: The Bundle, in the negotiated media type.
- `404`: Does not exist, or belongs to another tenant. The response is the same in both cases. (`Content-Language`)
- `406`: The `Accept` header, or `_format`, matches none of the served types. (`Content-Language`)
- `409`: Extraction is still running. Includes `Retry-After`. `not_ready`. Not the same as the `410`, which is final. (`Retry-After`, `Content-Language`)
- `410`: Expired, or deleted with `DELETE`. Final: retrying will not help. `expired`. (`Content-Language`)
- `422`: Extraction failed and there is no Bundle. The code is the same one the envelope would carry in `error`: `image_only_pdf`, `input_quality_too_low`, `not_a_lab_report`, `missing_collection_date` or `processing_error`. Final for this job. (`Content-Language`)

**Example**

```bash
curl https://laudos.precisa-saude.com.br/v1/extractions/$JOB_ID/bundle \
  -H "X-API-Key: $PRECISA_API_KEY" \
  -H "Accept: application/fhir+json"
```
