How Coatables verifies a Certificate of Analysis
Last updated: 15 June 2026
Upload a CoA PDF (digital or scanned). A vision model reads it; a deterministic layer recomputes the safety-critical values and cross-checks them against the lab's printed verdict. You get verified JSON and Excel.
The three steps
- Extract — Claude vision reads digital PDFs and scans natively: analyte, result, unit, spec limit, method, LOD/LOQ, and the lab's printed verdict.
- Verify — limit notation and units are normalized, then pass/fail is recomputed from the parsed numbers — not guessed by a model.
- Reconcile — the computed verdict is compared to the lab's printed one. Any disagreement, missing field, or low-confidence read is flagged.
What it supports
Third-party supplement lab reports — heavy metals, microbiology, contaminants — across a bounded set of common lab formats. It is deliberately not a general "any document" extractor; an unrecognized layout is flagged for review rather than silently guessed (and isn't charged).
Limit notation it reads
| Notation | Meaning |
|---|---|
| ≤ / < / ≥ / > | upper / lower bounds |
| NMT / NLT / max / min | not-more-than / not-less-than |
| X–Y | range |
| Absent in 25 g | microbial absence |
| ND / <LOQ | not detected / below quantitation |
Units are canonicalized and compared by class: mg/kg ↔ ppm, µg/kg ↔ ppb, mg/g ↔ %, and CFU/g classes. A result whose unit class doesn't match the analyte is flagged.
Output
JSON — the full structured result: document header, and per analyte the raw + parsed result, normalized unit, structured limit (min/max/type), method, LOD/LOQ, the lab's verdict, the computed verdict, whether they match, confidence, and flags.
XLSX — a Header sheet (document metadata) plus an Analytes sheet, one row per analyte with the same columns.
Verification flags
| Flag | Meaning |
|---|---|
| verdict_mismatch | our recomputed pass/fail disagrees with the lab's printed verdict |
| missing_limit | a result with no spec limit to check against |
| unparseable_limit | a limit string we couldn't normalize |
| ambiguous_unit | a unit we couldn't canonicalize (skipped for qualitative results) |
| unit_class_mismatch | the unit class doesn't fit the analyte |
| low_confidence | a low-confidence read (common on scans) — check it |
| no_analytes | document-level: nothing recognized as a supported CoA |
API
The tool is a thin client over one endpoint.
| Field | Value |
|---|---|
| POST | /api/extract |
| body | multipart/form-data, field file = the PDF (≤ 20 MB) |
| ?format=xlsx | returns the XLSX instead of JSON |
| Authorization | Bearer <credit key> (when billing is on) |
| X-Credits-Remaining | response header with your balance after the call |
| 402 / 400 / 500 | no/invalid key or out of credits / no file / extraction error |