---
title: "Read a laboratory certificate into its rows, each with its page, and check each row against a place's limits"
method: POST
path: "/certificate"
---

# Read a laboratory certificate into its rows, each with its page, and check each row against a place's limits

`POST /certificate`

## Request

- Base URL: `https://passage-data.com/api/v1`
- URL: `https://passage-data.com/api/v1/certificate`
- Auth: one of:
  - none
  - HTTP bearer
  - API key in header `x-api-key`

A PDF certificate, or the laboratory's own export as XLSX, XLS or CSV / TSV, read by rules (no AI writes a row): each row the certificate prints with the sample, the parameter, the value as printed, its unit, a qualifier (<, >, ND), the detection limit and the page (or sheet) it was read from; a sample's date, matrix, lab ID and certificate in `samples`. With a `place` (best with a `medium`), every row with a value is checked by check_values itself; `exceedances` lists every row over a limit or under a floor, each limit once in `limits` with its `cite`. A result below detection is not compared (`value.below_detection`). `checked_as` is the printed name without the lab's decoration (`Cuivre (Cu) a 2` -> `Cuivre`) when check_value holds that name. Rows page by `max_rows` / `offset` (the file is read again). Anything but a PDF, XLSX, XLS or CSV is refused `format.not_handled` before it is read. The file is read and discarded: it is not kept, and nothing is learned from it.

## Request body

- object
  - `content_base64` string: The file's bytes, base64-encoded (at most 10 MB). Give this or file_url.
  - `file_url` string: An https address the file can be fetched from (not loopback or a private network). Give this or content_base64.
  - `filename` string
  - `place` string: ISO 3166 code: US, US-OH, CA, CA-QC, DE, FR, IT, EU. Omit to read the rows without checking them.
  - `medium` 'water_discharge' | 'surface_water' | 'groundwater' | 'drinking_water' | 'soil' | 'ambient_air' | 'air_emission' | 'sediment' | 'leachate'
  - `statistic` string
  - `max_rows` integer: Certificate rows on this page; the summary and exceedances cover every row.
  - `offset` integer
  - `call_id` string: Only after read.pending: collect the reading without sending the file again.

## Response `200`

The rows read, the summary and, with a place, the exceedances and the limits they name; or a refusal about the file (certificate.no_rows, certificate.not_results, page.missing)

- Certificate
  - `query` object
  - `file` object
    - `name` string, nullable
    - `bytes` integer
    - `format` 'pdf' | 'xlsx' | 'xls' | 'csv'
    - `sha256` string
    - `from` string
  - `read` object: lab, certificates, samples, parameters, rows, and the pages (or sheets) rows were read from
  - `checked` object: places searched, or the place's own `refused` (the rows are still returned)
  - `summary` object
    - `samples` integer
    - `rows` integer
    - `over` integer
    - `under` integer
    - `within` integer
    - `not_comparable` integer
    - `below_detection` integer
    - `refused` integer
    - `refused_by` object
    - `says` string
  - `notes` object[]
    - `code` string
    - `says` string
  - `packed_as` string
  - `exceedances` object[]
    - `i` integer
    - `s` integer
    - `parameter` string
    - `value` string
    - `unit` string
    - `page` integer
    - `tab` string
    - `over` integer[]
    - `under` integer[]
  - `limits` Row[]
    - `parameter` string
    - `parameter_key` string
    - `value` string
    - `value_num` number, nullable
    - `range` unknown
    - `unit` string, nullable
    - `statistic` string, nullable
    - `direction` string, nullable
    - `kind` string
    - `medium` string
    - `place` string[]
    - `conditions` string[]
    - `cite` Cite
      - `instrument` string
      - `title` string
      - `issuer` string, nullable
      - `edition` string, nullable
      - `section` string, nullable
      - `page` union
        - integer
        - string
      - `quote` string, nullable
      - `source_url` string, nullable
      - `source_sha256` string, nullable
      - `source_status` object: What the daily edition watch last saw at the source: status unchanged, changed (with changed_on and says: the rows are as read from the edition named, not yet re-read) or not verified; checked is the day.
        - `checked` string
        - `status` 'unchanged' | 'changed' | 'not verified'
        - `changed_on` string
        - `says` string
      - `note` string: What the register prints beside the value (e.g. a later or earlier value and its date).
      - `table` string
      - `column` string: The register column the value sits in (e.g. mcl, mclg).
  - `refusals` object: Each refusal code's sentence, once
  - `sources_searched` object[]
  - `samples` object[]
    - `sample` string
    - `date` string
    - `matrix` string
    - `lab_id` string
    - `certificate` string
  - `total` integer
  - `offset` integer
  - `returned` integer
  - `next_offset` integer, nullable
  - `rows` object[]
    - `i` integer
    - `s` integer: index into samples
    - `parameter` string: as printed
    - `value` string: as printed
    - `unit` string
    - `qualifier` string: <, > or ND
    - `detection_limit` string
    - `rdl` string
    - `mdl` string
    - `loq` string
    - `flag` string
    - `lab_flag` string
    - `page` integer
    - `tab` string
    - `checked_as` string
    - `outcome` 'over' | 'under' | 'within' | 'not_comparable'
    - `counts` object
    - `over` integer[]
    - `under` integer[]
    - `refused` object
      - `code` string
      - `says` string
  - `refused` object

## Other responses

- `202`: read.pending: a long file is still being read; call again with its call_id
- `400`: file.missing, file.ambiguous, file.malformed, file.url_refused, call_id.unknown, body.malformed
- `413`: file.too_large (over 10 MB), body.too_large
- `415`: format.not_handled: not a PDF, XLSX, XLS or CSV; nothing read
- `422`: file.unreachable: the file_url could not be fetched
- `429`: engine.busy: the reading service's hourly limit
- `503`: engine.unreachable, engine.timeout: nothing was read; try again

---

[API](https://skmtc.dev/passage-data/apis/passage-data-cited-environmental-limits.md) · [All operations](https://skmtc.dev/passage-data/apis/passage-data-cited-environmental-limits/llms.txt) · [OpenAPI document](https://skmtc.dev/passage-data/apis/passage-data-cited-environmental-limits/revisions/ba066708bf53?raw)
