Turn an extraction into a filable certificate
const url = 'https://example.com/v1/certificate';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"document":"forsyth_3210_350.pdf","engine_version":"0.4.0","extraction":{"basis_of_bearings":"Deed Book 1150, Page 235","document_kind":"deed","legal_description_found":true,"tracts":[{"calls":[{"bearing":{"degrees":87,"ew":"E","minutes":30,"ns":"N","seconds":0},"call_type":"line","confidence":"high","distance":150,"monument":"iron stake","verbatim_text":"North 87 deg. 30\' East 150.0 feet"}],"distance_unit":"feet","is_exception":false,"name":"TRACT 1","pob_description":"BEGINNING at an iron stake in the eastern margin of Hinshaw Avenue","stated_acreage":0.17}]},"groundtruth_verdicts":[{"closure_ratio":"1:96,419","computed_acres":0.17,"geometry":{"acres":0.1702,"closure_denominator":96418.6,"misclosure":0.0062,"perimeter_ft":600,"vertices":[[0,0],[149.86,6.54],[0,0]]},"reasons":[],"status":"PASS","tract":"TRACT 1"}],"model":"claude-opus-5","usage":{"input_tokens":7373,"output_tokens":1140,"seconds":16.4}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/v1/certificate \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "document": "forsyth_3210_350.pdf", "engine_version": "0.4.0", "extraction": { "basis_of_bearings": "Deed Book 1150, Page 235", "document_kind": "deed", "legal_description_found": true, "tracts": [ { "calls": [ { "bearing": { "degrees": 87, "ew": "E", "minutes": 30, "ns": "N", "seconds": 0 }, "call_type": "line", "confidence": "high", "distance": 150, "monument": "iron stake", "verbatim_text": "North 87 deg. 30'\'' East 150.0 feet" } ], "distance_unit": "feet", "is_exception": false, "name": "TRACT 1", "pob_description": "BEGINNING at an iron stake in the eastern margin of Hinshaw Avenue", "stated_acreage": 0.17 } ] }, "groundtruth_verdicts": [ { "closure_ratio": "1:96,419", "computed_acres": 0.17, "geometry": { "acres": 0.1702, "closure_denominator": 96418.6, "misclosure": 0.0062, "perimeter_ft": 600, "vertices": [ [ 0, 0 ], [ 149.86, 6.54 ], [ 0, 0 ] ] }, "reasons": [], "status": "PASS", "tract": "TRACT 1" } ], "model": "claude-opus-5", "usage": { "input_tokens": 7373, "output_tokens": 1140, "seconds": 16.4 } }'Render a signed extraction as the certificate a buyer files.
Every tract with geometry is drawn as well as tabulated, in the traverse’s own plane and captioned as unplaced. That figure is a verification element rather than decoration: closure and stated acreage are both satisfied by a mirrored or quadrant-transposed boundary, and a reader with the instrument in hand is the only check that catches one.
Stateless like /v1/dxf: you post back the response you already hold,
and nothing here stores an extraction. Free and authenticated only, for
the same reason the DXF is: the document was already paid for and this
is a transform of it.
The difference is the signature. A renderer that printed whatever it was handed would let anyone edit REVIEW to PASS and print officialdom, so the attestation is recomputed over the posted body first and a payload that does not verify is refused rather than rendered.
The bytes that get verified are the ones that arrived, not
body.model_dump(). Re-validating first would drop any key this
version of the model does not know about and fill in defaults for any
it added, so a response signed before a deploy would stop verifying
after one. The typed parameter stays for the OpenAPI schema.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The extracted legal description.
object
object
object
object
The verification layer’s judgement on one tract.
Index-aligned with extraction.tracts: entry i judges tract i, and
the arrays are always the same length. Clients depend on that pairing.
This is the authoritative verdict. A client is not expected to recompute it — the rules and tolerances behind it are server-side.
object
PASS: traverse closes and corroborates stated acreage. REVIEW: something disagrees — a human decides. FAIL: the traverse could not be computed.
The unit the document measured in: “feet”, “meters”, “varas” or “chains”. Distances in extraction are as recorded, in this unit; the computed geometry is always feet, converted once.
The computed traverse, in its own local plane: POB at the origin, feet.
Everything a client does with a placed tract — previewing it under the cursor, writing it to a layer — is an affine transform of these vertices, so returning them is what lets a client be fully interactive without carrying the traverse math itself.
object
One point per call endpoint plus the start: N calls give N+1 vertices.
Distance from the last computed point back to the start.
Aliquot geometry located on the earth: lon/lat rings from the BLM CadNSDI PLSS fabric, quartered by the deed’s aliquot chain.
object
Always “EPSG:4326” today.
Exterior ring(s), lon/lat pairs.
CadNSDI PLSSID of the resolved township/range.
Provenance of the fabric, e.g. “BLM CadNSDI”.
Where the server thinks a metes-and-bounds tract sits.
A suggestion, never a result. status stays REVIEW even when every check
agreed, because the operator confirms a position with the same click they
already make; the product does not place a parcel on anybody’s behalf.
Filled only by POST /v1/locate. An /v1/extract response always carries
null here.
object
Always “EPSG:4326” today.
Which method produced this: “plss_corner” today.
The monument this was worked from, in words.
CadNSDI PLSSID, when one was resolved.
An operator supplied a better reading of the document and this verdict was recomputed over it. The status means exactly what it always means; corrections on the response says what changed, from what, and by whom.
This tract’s geometry was fitted after the fact by a compass rule. The status above did not move: it judges the recorded description, which still does not close. The adjusted figure is a drawing and export aid, and adjustments says how much misclosure was distributed.
The verification layer’s judgement on a document that has no tracts.
A recorded plat and a utility as-built get identified rather than traced:
the boundary a plat carries is drawn linework on a scan, and an as-built
states no coordinate basis at all. Both leave groundtruth_verdicts empty,
so this block is where their verdict lives.
Never PASS. These classes cannot corroborate anything, and the reasons say which gap applies rather than leaving “REVIEW” to be guessed at.
object
What the document is, e.g. “Plat” or “As-built”.
REVIEW: identified, with reasons naming what could not be verified. FAIL: nothing in the sheet identifies it.
One line naming the sheet, for a list or a header.
object
Wall-clock seconds spent in the extraction call.
Echo of the document name, if one was sent.
True when an east_test_ key produced this: a fixed sample extraction, no model call, no usage recorded, nothing billed. Declared here rather than dropped, because the attestation signs the whole body and a field the model discards would break every certificate.
Operator corrections applied to this extraction, in the order they were made. Empty on every /v1/extract response; filled only by /v1/corrections. Inside the attestation, so the log cannot be stripped from a certified payload without detection.
One field an operator changed, and what it read before.
The extraction is never overwritten. A correction rides beside it as an attributed log, the certificate prints the diff, and the attestation covers both — so a reader downstream can always see what a person changed and what the document as extracted actually said.
before is load-bearing twice over: it is what makes the certificate diff
reconstructible without a second copy of the extraction, and it is the
staleness guard. A client holding an old copy of a re-extracted document
names a before that no longer matches and is refused rather than
silently overwriting a value nobody saw.
object
Index into extraction.tracts.
Index into that tract’s calls (or tie_calls).
What the field read before. Must match the current value or the correction is refused as stale.
What it should read. Same shape as before.
Which list call indexes.
Who made the change. Recorded, never verified.
When, ISO 8601 UTC. Server-stamped if omitted.
Why — e.g. “source text reads 149.5”.
Compass-rule adjustments applied to this extraction’s geometry. The verdicts above judge the recorded description and were NOT recomputed over these.
A disclosed best fit over courses that do not close.
This never moves a verdict. The recorded description still miscloses, and the verdict on the tract still says so; the adjusted geometry is a drawing and export aid. An adjustment whose size nobody downstream can see is the silent auto-close this product exists to refuse, which is why the misclosure it distributed is carried here and printed on the certificate.
object
Index into extraction.tracts.
The Bowditch rule: misclosure apportioned by course length.
Feet of misclosure that were distributed.
Feet of perimeter it was spread over.
What lets this response be certified later without being trusted.
POST /v1/certificate is stateless: you post back the response you already
hold. That endpoint recomputes this signature over the body it received and
refuses anything that does not match, so a certificate cannot be produced
by editing a verdict in a text editor. Keep the response as the API
returned it; re-serializing is fine, editing is not.
object
SHA-256 of the document as extracted, after any image-to-PDF normalization. Empty on a batch document submitted before this field existed.
UTC timestamp of the extraction, RFC 3339.
HMAC-SHA256 over the response body with attestation excluded, serialized with sorted keys and compact separators. Empty if the server had no signing key configured.
Responses
Section titled “Responses”A GroundTruth Certificate of Digitization.
Missing, unknown, or revoked API key
object
object
Example
{ "error": { "documents_used": 200, "message": "Monthly document quota reached: 200 of 200 documents used this month. The cap resets on 2026-08-01. Email sales@easting.ai to raise it.", "monthly_cap": 200, "resets_on": "2026-08-01", "type": "quota_exceeded" }}The attestation is missing or does not match, or the extraction certifies nothing
object
object
Example
{ "error": { "documents_used": 200, "message": "Monthly document quota reached: 200 of 200 documents used this month. The cap resets on 2026-08-01. Email sales@easting.ai to raise it.", "monthly_cap": 200, "resets_on": "2026-08-01", "type": "quota_exceeded" }}