PDFCraft
Extraction guide

Extract a certificate of analysis PDF to JSON

Everything below is the response the live API returned for a certificate of analysis, generated when this page was built. Not a description of what it would return — the output itself, including what it scored low on.

Who this is for

Quality systems in pharma, food and chemicals, where every incoming batch arrives with a CoA that has to be filed and checked.

The job, in the words people search for: “read a certificate of analysis into a QMS”.

What makes this shape awkward

A specification column of text ranges ("≤ 0.5%", "NLT 98.0%") beside a result column of plain numbers. The specification is deliberately not a number, and a loose parser that reads "0.5" out of "≤ 0.5%" makes both columns look numeric and loses the contrast that identifies the header.

The measured result

Pages read1
Time8 ms (8 ms/page)
Labelled fields found unprompted5
Tables1 (6 rows)
Inputcertificate-of-analysis.pdf (31 KB — synthetic, generated from a spec in the repo; real documents of this type cannot be published)

Fields, with a schema

Ask for the fields you want by the label printed on the page. Each answer carries the text exactly as printed (raw), the value coerced to the type you asked for, a confidence, and a bounding box you can draw on the page to check it.

curl -X POST https://api.pdfcraft.dev/v1/extract \
  -H "Authorization: Bearer $PDFCRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": "<base64 of your certificate of analysis>",
    "schema": {
      "batch_number": "string",
      "manufactured": "date",
      "expiry": "date"
    }
  }'

What came back

Fieldrawvalueconfidence
batch_numberB-2026-04417"B-2026-04417"60%
manufactured2026-01-22"2026-01-22"70%
expiry2029-01-21"2029-01-21"70%

Every label it found without being asked

Send no schema and you get all of these, keyed by the label as printed. Useful for discovering what a new supplier’s layout actually contains before you write a schema against it.

Product, Batch Number, Manufactured, Expiry, Released By

Tables

Test · Method · Specification · Result · Pass · 6 rows × 5 columns · page 1

Table confidence 90%. Header confidence 100% — the first row was promoted out of the data.

TestMethodSpecificationResultPass
AssayHPLCNLT 98.0%99.4Yes
Water contentKarl Fischer11.0 - 13.0%12.1Yes
Heavy metalsICP-MSNMT 10 ppm2Yes

First 3 of 6 rows.

With options.rows_as_objects, the same row keyed by its header:

{
  "Test": "Assay",
  "Method": "HPLC",
  "Specification": "NLT 98.0%",
  "Result": "99.4",
  "Pass": "Yes"
}

What this does not do

Try it on your own certificate of analysis

The playground takes a file and shows the same JSON, with every bounding box drawn over the page. No key and no signup for the first few; a free key gives you 100 pages a month.

Related