{"openapi":"3.1.0","info":{"title":"Easting API","description":"Hosted document extraction with GroundTruth verdicts. Authenticate with `Authorization: Bearer east_live_...`.","version":"0.8.0"},"paths":{"/v1/extract":{"post":{"summary":"Extract a legal description from a recorded document","operationId":"extract_v1_extract_post","security":[{"EastingApiKey":[]}],"parameters":[{"name":"X-Document-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filename to record in the usage ledger and echo back. Optional; multipart uploads take it from the file part instead.","examples":["forsyth_3210_350.pdf"],"title":"X-Document-Name"},"description":"Filename to record in the usage ledger and echo back. Optional; multipart uploads take it from the file part instead."},{"name":"X-Upload-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A slot from POST /v1/uploads, for a document too big to send in one request. Send an empty body with it. The staged object is deleted as it is read.","examples":["upl_9dK2mQxLp7ZvR4tYbN6w"],"title":"X-Upload-Id"},"description":"A slot from POST /v1/uploads, for a document too big to send in one request. Send an empty body with it. The staged object is deleted as it is read."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"description":"The recorded document: PDF, TIFF (including multi-page), PNG, or JPEG. Images are converted to PDF on arrival. Maximum 4.5 MB, which is what the hosting platform will carry in one request; stage anything larger through POST /v1/uploads. A legal description already held as text posts as `text/plain` instead (UTF-8, up to 64 KB) — the response then carries `input_kind: \"text\"` and the attestation hashes the text itself.","content":{"application/pdf":{"schema":{"type":"string","format":"binary","description":"Raw PDF bytes. Pair with an X-Document-Name header."}},"text/plain":{"schema":{"type":"string","description":"A pasted legal description, UTF-8, up to 64 KB. Pair with an X-Document-Name header; unnamed text is recorded as pasted-text."}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The document. Its filename becomes the ledger entry."}},"required":["file"]}}}}}},"/v1/uploads":{"post":{"summary":"Get a slot to upload a large document into","description":"Stage a document too big to send in one request.\n\nPUT the PDF to the returned URL, then call `/v1/extract` with\n`X-Upload-Id`. The object is deleted as it is read, on every tier, so\nstaging is transport rather than storage: nothing survives the\nextraction that consumed it, and an upload nobody extracts expires on\nits own.\n\nAvailable on every plan. The documents that need this are the large\nscanned as-builts that government and compliance-sensitive buyers work\nwith, which is the last group that should be locked out of it.","operationId":"create_upload_v1_uploads_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UploadRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"That upload id is not the caller's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such upload; ids work exactly once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the staged size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Staging is not configured on this server","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/dxf":{"post":{"summary":"Convert an extraction into a CAD drawing","description":"Turn a `/v1/extract` response into a DXF.\n\nStateless on purpose: the customer posts back the JSON they already\nhold. Nothing on this server stores an extraction, and inventing a\nplace to keep one would reopen the retention question the privacy page\nsettles.\n\nFree, and authenticated only. The extraction was already paid for and\nthis is a transform of it, so metering the conversion would be charging\ntwice for one document. No quota check either, which means a customer\nat their cap can still collect work they have already bought.","operationId":"to_dxf_v1_dxf_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}},"required":true},"responses":{"200":{"description":"An R2010 DXF drawing of every tract with geometry.","content":{"application/dxf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Nothing in the extraction has geometry to draw","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/geopackage":{"post":{"summary":"Convert an extraction into a GeoPackage","description":"Turn a `/v1/extract` response into a GeoPackage.\n\nThe GIS twin of `/v1/dxf`, and it holds the same bargain: stateless\n(the customer posts back the JSON they already hold), free, and\nauthenticated only — the extraction was already paid for, and a\ncustomer at their cap can still collect work they bought.\n\nLocal-plane tracts land under GeoPackage's undefined Cartesian SRS\n(POB at the origin, feet); aliquot tracts the PLSS fabric located\nadditionally land in a WGS 84 layer that opens placed.","operationId":"to_geopackage_v1_geopackage_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}},"required":true},"responses":{"200":{"description":"A GeoPackage with tract and call layers for every tract with geometry, plus a WGS 84 layer for tracts the PLSS fabric located.","content":{"application/geopackage+sqlite3":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Nothing in the extraction has geometry to draw","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/locate":{"post":{"summary":"Suggest where an extraction's tracts sit","description":"Fill each tract's `location` block, and re-sign what comes back.\n\nA deed defines shape, not position. Today the one source that needs no\ninput from you is `plss_corner`: when a description commences at a\nsection or aliquot corner, the fabric resolves that corner and the tie\nchain walks to the point of beginning. Most descriptions name no such\ncorner, and a response with every `location` null is the ordinary\nanswer rather than a failure.\n\n**A suggestion, never a placement.** The block carries REVIEW even when\nevery check agreed, and `pob` comes back null whenever the traverse\nfailed its own containment test, with `reasons` naming the corner that\nwas tried. Confirming a position stays the operator's click.\n\nStateless and free, like `/v1/dxf` and `/v1/certificate`: you post back\nthe response you already hold, nothing here stores it, and the document\nwas paid for once already.\n\nThe signature is the point of doing this server-side. The posted\nattestation is verified first, every `location` block that arrived is\ndiscarded before anything is computed, and only what this server\nworked out is signed into the response. Otherwise a coordinate typed\ninto a text editor could ride a valid signature onto a certificate.","operationId":"locate_extraction_v1_locate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocateResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The attestation is missing or does not match, or the requested source is not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/corrections":{"post":{"summary":"Apply operator corrections to an extraction","description":"Correct what we misread, or fit courses that do not close — and\nre-sign, so the deliverable survives the edit.\n\nTwo different acts, and the response keeps them apart:\n\n**A correction** says the document reads something else and we got it\nwrong. That is a better reading of the same document, so the verdicts\ncome back recomputed over the corrected values and mean exactly what\nthey always meant. The tract's verdict carries `corrected: true`.\n\n**An adjustment** (`adjust: [0]`) distributes a tract's misclosure by\ncompass rule. **The verdict does not move.** It judges the recorded\ndescription, which still miscloses, and the adjusted figure is a\ndrawing and export aid — the geometry changes, `status`,\n`closure_ratio` and the reasons do not. Never automatic: you ask for it\nper tract.\n\nNothing is overwritten. The extraction comes back carrying the\ncorrected values *and* a `corrections` log naming every field, what it\nread before, who changed it and why, plus an `adjustments` list\ncarrying the misclosure each fit distributed. The certificate prints\nboth. That log is inside the attestation, so it cannot be stripped from\na certified payload without breaking the signature.\n\nStateless and free, like `/v1/locate` and `/v1/certificate`: you post\nback the response you already hold, nothing here stores it, and the\ndocument was paid for once already.\n\nA correction whose `before` disagrees with what the extraction\ncurrently holds is refused as `stale_correction` rather than applied —\nthat is a client editing a document somebody else has since\nre-extracted, and overwriting there would discard a value the operator\nnever saw. Every `corrections`, `adjustments` and corrected/adjusted\nflag on the posted body is discarded before anything is computed, for\nthe same reason `/v1/locate` drops incoming positions.","operationId":"correct_extraction_v1_corrections_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The attestation is missing or does not match, a correction names a value the extraction no longer holds, or there is nothing to do","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/certificate":{"post":{"summary":"Turn an extraction into a filable certificate","description":"Render a signed extraction as the certificate a buyer files.\n\nEvery tract with geometry is drawn as well as tabulated, in the\ntraverse's own plane and captioned as unplaced. That figure is a\nverification element rather than decoration: closure and stated\nacreage are both satisfied by a mirrored or quadrant-transposed\nboundary, and a reader with the instrument in hand is the only check\nthat catches one.\n\nStateless like `/v1/dxf`: you post back the response you already hold,\nand nothing here stores an extraction. Free and authenticated only, for\nthe same reason the DXF is: the document was already paid for and this\nis a transform of it.\n\nThe difference is the signature. A renderer that printed whatever it\nwas handed would let anyone edit REVIEW to PASS and print officialdom,\nso the attestation is recomputed over the posted body first and a\npayload that does not verify is refused rather than rendered.\n\nThe bytes that get verified are the ones that arrived, not\n`body.model_dump()`. Re-validating first would drop any key this\nversion of the model does not know about and fill in defaults for any\nit added, so a response signed before a deploy would stop verifying\nafter one. The typed parameter stays for the OpenAPI schema.","operationId":"to_certificate_v1_certificate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}},"required":true},"responses":{"200":{"description":"A GroundTruth Certificate of Digitization.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The attestation is missing or does not match, or the extraction certifies nothing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/usage":{"get":{"summary":"This month's document count and token totals","operationId":"usage_v1_usage_get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Report on one of your other keys instead of this one. A customer running a key per environment attributes spend this way. Only keys owned by the same account are visible; anything else is a 404, never a hint that the key exists.","examples":["east_live_AbCdEfGh"],"title":"Prefix"},"description":"Report on one of your other keys instead of this one. A customer running a key per environment attributes spend this way. Only keys owned by the same account are visible; anything else is a 404, never a hint that the key exists."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/healthz":{"get":{"summary":"Liveness and DB reachability","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/batches":{"get":{"tags":["batch"],"summary":"This key's recent batches","operationId":"list_batches_v1_batches_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchListResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]},"post":{"tags":["batch"],"summary":"Submit several documents for batch extraction","description":"Hand every PDF to the extraction service at once and return a batch id.\n\nBatch trades latency for cost: most batches settle inside an hour and the\nceiling is 24. It is the right home for a document too slow to finish\ninside a synchronous request, and for a stack of them at once.","operationId":"submit_batch_v1_batches_post","requestBody":{"description":"The documents in one multipart body: PDFs, images, or plain-text legal descriptions (the kind is read from each part's bytes).","content":{"multipart/form-data":{"schema":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","description":"One part per PDF. Filenames become the document names in the results."}},"type":"object","required":["files"]}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchSubmitResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such batch under this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The batch has not ended yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"Results deleted by the account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/batches/{batch_id}":{"get":{"tags":["batch"],"summary":"Whether a batch has ended, and how its documents fared","description":"Poll here. Answers over quota, the same way `/v1/usage` does: a customer\nwho has hit their cap still needs to collect work already paid for.","operationId":"batch_status_v1_batches__batch_id__get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchStatusResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such batch under this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The batch has not ended yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"Results deleted by the account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/batches/{batch_id}/results":{"get":{"tags":["batch"],"summary":"Every document's extraction and verdicts","description":"Every document of a settled batch, in one response.\n\nCollecting starts no clock. Results are kept until somebody on the account\ndeletes them through `/v1/runs`, so a folder can be collected again after\na browser tab dies.\n\n**A key collects its own batches; a signed-in person collects the\naccount's**, which is what lets a folder run from the plugin open in the\nbrowser.","operationId":"batch_results_v1_batches__batch_id__results_get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResultsResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such batch under this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The batch has not ended yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"Results deleted by the account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/extracts":{"post":{"tags":["extract"],"summary":"Submit one document for asynchronous extraction","description":"Take the document, answer immediately, extract it elsewhere.\n\nUse this for anything that might run long, which in practice is any\nmulti-tract instrument. A small deed works here too and costs a couple of\nseconds of polling over the synchronous path.\n\nThe quota check happens now rather than when the worker finishes, so a job\nnever settles into a bill the customer never agreed to. Jobs already in\nflight count against the allowance, batch items included.","operationId":"submit_extract_v1_extracts_post","security":[{"EastingApiKey":[]}],"parameters":[{"name":"X-Document-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filename to record and echo back.","examples":["citrus_fl_2025014130.pdf"],"title":"X-Document-Name"},"description":"Filename to record and echo back."},{"name":"X-Upload-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A slot from POST /v1/uploads, for a document too big to send in one request. Send an empty body with it.","title":"X-Upload-Id"},"description":"A slot from POST /v1/uploads, for a document too big to send in one request. Send an empty body with it."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractJobSubmitResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such job under this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"Result deleted by the account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":false,"description":"The document, exactly as `/v1/extract` takes it: raw bytes with an optional `X-Document-Name`, a pasted legal description as `text/plain`, a multipart `file` part, or nothing at all when `X-Upload-Id` names a staged document.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"text/plain":{"schema":{"type":"string"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/v1/extracts/{job_id}":{"get":{"tags":["extract"],"summary":"Poll one asynchronous extraction, and collect it","description":"Status while it runs, the whole extraction when it is done.\n\nPoll every few seconds. A typical deed settles in well under a minute; the\nslowest instrument in our corpus takes about four.\n\nCollecting does not start a clock. The result stays until you delete it\nthrough `/v1/runs`, and the listing there says what is still held.\n\n**A key collects its own work; a signed-in person collects the account's.**\nSo a deed extracted through the plugin opens in the browser, which is what\na customer means by having an account rather than a credential. A key\nstill reaches nothing but what it submitted.","operationId":"extract_status_v1_extracts__job_id__get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractJobStatusResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Monthly document cap or overage ceiling reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Document over the size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such job under this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Extraction failed upstream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"Result deleted by the account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/runs":{"get":{"tags":["runs"],"summary":"Every document this account has run","description":"Newest first, across every key you own, singles and batch documents both.\n\nA run that failed stays in the list. So does one you have deleted, with\n`result_available` false, because the record that the work happened is\nworth keeping after the work itself is gone.","operationId":"list_runs_v1_runs_get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunListResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["runs"],"summary":"Delete every result this account still holds","description":"Clear every result across every key you own, in one call.\n\nThe history stays: each run keeps its name, its date, and its verdict\nstatus, with `result_available` false. What goes is every extraction body\nthis account has left on the server.","operationId":"purge_runs_v1_runs_delete","security":[{"EastingApiKey":[]}],"parameters":[{"name":"confirm","in":"query","required":false,"schema":{"type":"string","description":"Must be `delete-everything`. A purge is not something to reach by accident, so an empty path does nothing on its own.","default":"","title":"Confirm"},"description":"Must be `delete-everything`. A purge is not something to reach by accident, so an empty path does nothing on its own."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRunsResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/runs/{reference}":{"delete":{"tags":["runs"],"summary":"Delete one run's result","description":"Clear what was extracted, and keep the record that it was.\n\nThe reference is the one the listing gives you: a job id for a single\ndocument, a batch id for anything that came out of a folder. Deleting a\nbatched document clears the whole batch, which is the honest reading of a\nreference that names the batch rather than the item.\n\nA reference this account does not own answers 404, the same as one that\nnever existed, so a guessed id cannot confirm somebody else's run.","operationId":"delete_run_v1_runs__reference__delete","security":[{"EastingApiKey":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRunsResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/runs/{reference}/placements":{"get":{"tags":["runs"],"summary":"Where this run's tracts were placed","description":"The positions an operator saved, document then tract order.\n\nAn empty list means the run exists and nobody has put it on a map yet,\nwhich is the ordinary case: extraction and placement are two acts, and\nplenty of documents are read without ever being located.","operationId":"read_placements_v1_runs__reference__placements_get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementListResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["runs"],"summary":"Save where this run's tracts sit","description":"Replace the positions named, and leave the rest alone.\n\nPlacing a tract again moves it: the second click corrects the first rather\nthan adding to a list of places the parcel has been. Tracts left out of\nthe body keep whatever they had, so a surface can save the one parcel\nsomebody just placed.\n\nThe document name is checked against the run rather than trusted. A name\nthe reference does not cover is a 404, because a position filed under a\ndocument that does not exist would be saved where nothing will look for\nit.","operationId":"save_placements_v1_runs__reference__placements_put","security":[{"EastingApiKey":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"document","in":"query","required":false,"schema":{"type":"string","description":"Which document of the run, required when the reference is a batch. A single extraction has one document and needs no name.","default":"","title":"Document"},"description":"Which document of the run, required when the reference is a batch. A single extraction has one document and needs no name."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementListResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Body was empty, or was not a PDF, TIFF, PNG, or JPEG","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["runs"],"summary":"Forget where this run was placed","description":"Drop the positions and keep the extraction.\n\nThe narrow half of delete, for an operator who placed a parcel wrongly and\nwants the record of that gone without paying to extract the deed again.\nDeleting the run itself takes the positions with it, so this route is for\nthe case where the geometry is still wanted.","operationId":"forget_placements_v1_runs__reference__placements_delete","security":[{"EastingApiKey":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRunsResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fabric":{"get":{"tags":["export"],"summary":"Every parcel this account has located, as one GeoPackage","description":"Assemble every parcel this account has put on the earth.\n\nOne polygon layer, `parcels`, in WGS 84, and a `parcel_boundaries` line\nlayer where each course keeps the sentence it was read from. A parcel\narrives one of two ways and `located_by` says which: `operator` for one\nsomebody placed by hand, `plss` for an aliquot tract the survey fabric\nlocated. They are different kinds of claim, and a map that merged them\nwould look equally confident about both.\n\nNothing is recomputed. The geometry is each verdict's own traverse and the\nattributes are the document's own words, so this file says exactly what\nthe results it was built from say.\n\nFree and unmetered: the documents were paid for when they were extracted.","operationId":"account_fabric_v1_fabric_get","security":[{"EastingApiKey":[]}],"parameters":[{"name":"runs","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"How many of the most recent runs to assemble from.","default":500,"title":"Runs"},"description":"How many of the most recent runs to assemble from."}],"responses":{"200":{"description":"A WGS 84 GeoPackage: one polygon per located parcel, plus a boundary layer carrying each call's verbatim text.","content":{"application/geopackage+sqlite3":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"This account has not located anything yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/checkout":{"get":{"tags":["billing"],"summary":"Start a subscription (redirects to Stripe Checkout)","operationId":"checkout_billing_checkout_get","parameters":[{"name":"tier","in":"query","required":false,"schema":{"type":"string","description":"`standard`, `metered`, or `pack-10`.","default":"standard","title":"Tier"},"description":"`standard`, `metered`, or `pack-10`."},{"name":"seats","in":"query","required":false,"schema":{"type":"integer","description":"Seats to start with; changeable in Checkout.","default":1,"title":"Seats"},"description":"Seats to start with; changeable in Checkout."},{"name":"quantity","in":"query","required":false,"schema":{"type":"integer","description":"Packs to buy (pack tiers); changeable in Checkout.","default":1,"title":"Quantity"},"description":"Packs to buy (pack tiers); changeable in Checkout."}],"responses":{"307":{"description":"Successful Response"},"303":{"description":"Redirect to the Stripe-hosted page"},"422":{"description":"Unknown tier or seat count out of range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Stripe did not return a URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Self-serve billing is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/billing/claim":{"get":{"tags":["billing"],"summary":"Show the API key for a completed checkout (once)","operationId":"claim_billing_claim_get","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string","description":"The Checkout session id Stripe redirected with.","title":"Session Id"},"description":"The Checkout session id Stripe redirected with."}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"303":{"description":"Redirect to the Stripe-hosted page"},"422":{"description":"Unknown tier or seat count out of range","content":{"text/html":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Stripe did not return a URL","content":{"text/html":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Self-serve billing is not configured","content":{"text/html":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/billing/portal":{"get":{"tags":["billing"],"summary":"Manage the subscription (redirects to Stripe's billing portal)","operationId":"portal_billing_portal_get","responses":{"307":{"description":"Successful Response"},"303":{"description":"Redirect to the Stripe-hosted page"},"422":{"description":"Unknown tier or seat count out of range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Stripe did not return a URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Self-serve billing is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/keys":{"get":{"tags":["keys"],"summary":"Every key this account owns","description":"Revoked keys stay in the list on purpose: the credential someone\nretired last week is exactly the one they want to see when they are\nworking out what is still live.","operationId":"list_keys_v1_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyListResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]},"post":{"tags":["keys"],"summary":"Mint another key for this account","description":"The plaintext is shown once here and never stored, the same promise the\nclaim page makes. There is no recovery path by design; there is a rotate\nbutton instead.","operationId":"mint_v1_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserKeyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The request conflicts with current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/keys/{prefix}/rotate":{"post":{"tags":["keys"],"summary":"Replace one key with a successor","description":"Same name, same caps, same owner, new secret, old one revoked, in one\ntransaction. A rotated key that came back unowned would vanish from this\nlist at the one moment its owner is looking at it.","operationId":"rotate_v1_keys__prefix__rotate_post","security":[{"EastingApiKey":[]}],"parameters":[{"name":"prefix","in":"path","required":true,"schema":{"type":"string","title":"Prefix"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/{prefix}":{"delete":{"tags":["keys"],"summary":"Revoke one key","description":"Soft, like every revocation here: the row stays so the usage history\nbehind an invoice survives the credential.","operationId":"revoke_v1_keys__prefix__delete","security":[{"EastingApiKey":[]}],"parameters":[{"name":"prefix","in":"path","required":true,"schema":{"type":"string","title":"Prefix"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such resource under this caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/whoami":{"get":{"tags":["keys"],"summary":"Who this token belongs to","description":"What the portal needs on first paint: who you are, whether you are\nstaff, and how many live credentials you hold.","operationId":"whoami_v1_keys_whoami_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoAmIResponse"}}}},"401":{"description":"Missing, unknown, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"EastingApiKey":[]}]}},"/v1/signup":{"post":{"tags":["signup"],"summary":"Start free: three documents, no card","operationId":"signup_v1_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"400":{"description":"The request was understood and refused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited; read Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Extraction temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Adjustment":{"properties":{"tract":{"type":"integer","title":"Tract","description":"Index into `extraction.tracts`."},"method":{"type":"string","const":"compass_rule","title":"Method","description":"The Bowditch rule: misclosure apportioned by course length.","default":"compass_rule"},"misclosure_before":{"type":"number","title":"Misclosure Before","description":"Feet of misclosure that were distributed.","default":0.0},"perimeter":{"type":"number","title":"Perimeter","description":"Feet of perimeter it was spread over.","default":0.0},"by":{"type":"string","title":"By","default":""},"at":{"type":"string","title":"At","default":""}},"type":"object","required":["tract"],"title":"Adjustment","description":"A disclosed best fit over courses that do not close.\n\n**This never moves a verdict.** The recorded description still miscloses,\nand the verdict on the tract still says so; the adjusted geometry is a\ndrawing and export aid. An adjustment whose size nobody downstream can see\nis the silent auto-close this product exists to refuse, which is why the\nmisclosure it distributed is carried here and printed on the certificate."},"Attestation":{"properties":{"document_sha256":{"type":"string","title":"Document Sha256","description":"SHA-256 of the document as extracted, after any image-to-PDF normalization. Empty on a batch document submitted before this field existed."},"extracted_at":{"type":"string","title":"Extracted At","description":"UTC timestamp of the extraction, RFC 3339."},"signature":{"type":"string","title":"Signature","description":"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."}},"type":"object","required":["document_sha256","extracted_at","signature"],"title":"Attestation","description":"What lets this response be certified later without being trusted.\n\n`POST /v1/certificate` is stateless: you post back the response you already\nhold. That endpoint recomputes this signature over the body it received and\nrefuses anything that does not match, so a certificate cannot be produced\nby editing a verdict in a text editor. Keep the response as the API\nreturned it; re-serializing is fine, editing is not."},"BatchItemSummary":{"properties":{"document":{"type":"string","title":"Document"},"status":{"type":"string","enum":["queued","succeeded","failed"],"title":"Status"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why this document failed. Failed documents are never billed."}},"type":"object","required":["document","status"],"title":"BatchItemSummary","description":"One document inside a batch, without its result."},"BatchListResponse":{"properties":{"batches":{"items":{"$ref":"#/components/schemas/BatchStatusResponse"},"type":"array","title":"Batches"}},"type":"object","required":["batches"],"title":"BatchListResponse"},"BatchResult":{"properties":{"document":{"type":"string","title":"Document"},"status":{"type":"string","enum":["queued","succeeded","failed"],"title":"Status"},"result":{"$comment":"ExtractResponse, returned unmodified","anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"The `/v1/extract` body verbatim, exactly as it was signed."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["document","status"],"title":"BatchResult","description":"One document's outcome. Exactly one of `result` and `error` is set.\n\n`result` is a raw object for the reason `ExtractJobStatusResponse.result`\nspells out: re-serializing a stored body through a model injects defaults\nfor fields added since it was signed, and the signature covers every key."},"BatchResultsResponse":{"properties":{"batch":{"type":"string","title":"Batch"},"documents":{"type":"integer","title":"Documents"},"results":{"items":{"$ref":"#/components/schemas/BatchResult"},"type":"array","title":"Results"}},"type":"object","required":["batch","documents","results"],"title":"BatchResultsResponse"},"BatchStatusResponse":{"properties":{"batch":{"type":"string","title":"Batch"},"status":{"type":"string","enum":["in_progress","ended","failed"],"title":"Status"},"documents":{"type":"integer","title":"Documents"},"succeeded":{"type":"integer","title":"Succeeded"},"failed":{"type":"integer","title":"Failed"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"results_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Results Url","description":"Present once the batch has ended."},"items":{"items":{"$ref":"#/components/schemas/BatchItemSummary"},"type":"array","title":"Items"}},"type":"object","required":["batch","status","documents","succeeded","failed","submitted_at"],"title":"BatchStatusResponse"},"BatchSubmitResponse":{"properties":{"batch":{"type":"string","title":"Batch"},"status":{"type":"string","enum":["in_progress","ended","failed"],"title":"Status"},"documents":{"type":"integer","title":"Documents"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"poll_url":{"type":"string","title":"Poll Url","description":"Poll here until status is `ended`, then fetch the results."}},"type":"object","required":["batch","status","documents","submitted_at","poll_url"],"title":"BatchSubmitResponse","examples":[{"batch":"batch_7QhKm2vXpLd9","documents":3,"poll_url":"https://api.easting.ai/v1/batches/batch_7QhKm2vXpLd9","status":"in_progress","submitted_at":"2026-08-07T14:02:11Z"}]},"CorrectRequest":{"properties":{"extraction":{"additionalProperties":true,"type":"object","title":"Extraction","description":"A `/v1/extract` (or `/v1/corrections`) response, exactly as the API returned it. Its attestation is verified before anything is computed, and any `corrections`, `adjustments` or corrected/adjusted flags it carries are discarded rather than trusted."},"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array","title":"Corrections","description":"Field-level corrections to apply. The verdicts are recomputed over the corrected values."},"adjust":{"items":{"type":"integer"},"type":"array","title":"Adjust","description":"Indices of tracts to close by compass rule, **after** the verdicts are computed. Never automatic: an adjustment nobody asked for is the silent version by another name."},"by":{"type":"string","title":"By","description":"Who is making these changes. Applied to any correction or adjustment that does not name its own.","default":""}},"type":"object","required":["extraction"],"title":"CorrectRequest","description":"A request to correct an extraction you already hold, or to adjust it."},"CorrectResponse":{"properties":{"extraction":{"additionalProperties":true,"type":"object","title":"Extraction"},"corrected":{"type":"integer","title":"Corrected","description":"How many tracts were recomputed over a correction."},"adjusted":{"type":"integer","title":"Adjusted","description":"How many tracts had geometry fitted."}},"type":"object","required":["extraction","corrected","adjusted"],"title":"CorrectResponse","description":"The posted extraction, corrected and re-signed."},"Correction":{"properties":{"tract":{"type":"integer","title":"Tract","description":"Index into `extraction.tracts`."},"call":{"type":"integer","title":"Call","description":"Index into that tract's `calls` (or `tie_calls`)."},"field":{"type":"string","enum":["distance","chord_length","radius","arc_length","bearing","chord_bearing","curve_direction"],"title":"Field"},"before":{"title":"Before","description":"What the field read before. Must match the current value or the correction is refused as stale."},"after":{"title":"After","description":"What it should read. Same shape as `before`."},"kind":{"type":"string","enum":["boundary","tie"],"title":"Kind","description":"Which list `call` indexes.","default":"boundary"},"by":{"type":"string","title":"By","description":"Who made the change. Recorded, never verified.","default":""},"at":{"type":"string","title":"At","description":"When, ISO 8601 UTC. Server-stamped if omitted.","default":""},"note":{"type":"string","title":"Note","description":"Why — e.g. \"source text reads 149.5\".","default":""}},"type":"object","required":["tract","call","field","before","after"],"title":"Correction","description":"One field an operator changed, and what it read before.\n\nThe extraction is never overwritten. A correction rides beside it as an\nattributed log, the certificate prints the diff, and the attestation covers\nboth — so a reader downstream can always see what a person changed and what\nthe document as extracted actually said.\n\n`before` is load-bearing twice over: it is what makes the certificate diff\nreconstructible without a second copy of the extraction, and it is the\nstaleness guard. A client holding an old copy of a re-extracted document\nnames a `before` that no longer matches and is refused rather than\nsilently overwriting a value nobody saw."},"DeleteRunsResponse":{"properties":{"cleared":{"type":"integer","title":"Cleared","description":"Result bodies removed. Zero means there was none left."},"placements":{"type":"integer","title":"Placements","description":"Tract positions forgotten with them. A placement whose geometry is gone points at nothing, so it goes too.","default":0}},"type":"object","required":["cleared"],"title":"DeleteRunsResponse","description":"What a delete actually removed.\n\n`cleared` counts result bodies, not runs: deleting a batch clears every\ndocument inside it, so one reference can answer with fifty. The rows\nthemselves stay, which is why the listing keeps showing what ran.","examples":[{"cleared":1,"placements":2}]},"DocumentVerdict":{"properties":{"document":{"type":"string","title":"Document","description":"What the document is, e.g. \"Plat\" or \"As-built\"."},"status":{"type":"string","enum":["REVIEW","FAIL"],"title":"Status","description":"REVIEW: identified, with reasons naming what could not be verified. FAIL: nothing in the sheet identifies it."},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons"},"summary":{"type":"string","title":"Summary","description":"One line naming the sheet, for a list or a header.","default":""}},"type":"object","required":["document","status"],"title":"DocumentVerdict","description":"The verification layer's judgement on a document that has no tracts.\n\nA recorded plat and a utility as-built get identified rather than traced:\nthe boundary a plat carries is drawn linework on a scan, and an as-built\nstates no coordinate basis at all. Both leave `groundtruth_verdicts` empty,\nso this block is where their verdict lives.\n\n**Never PASS.** These classes cannot corroborate anything, and the reasons\nsay which gap applies rather than leaving \"REVIEW\" to be guessed at."},"ErrorDetail":{"properties":{"type":{"type":"string","title":"Type"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["type","message"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}},"type":"object","required":["error"],"title":"ErrorResponse","examples":[{"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"}}]},"ExtractJobStatusResponse":{"properties":{"job":{"type":"string","title":"Job"},"status":{"type":"string","enum":["queued","running","succeeded","failed"],"title":"Status"},"document":{"type":"string","title":"Document"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"result":{"$comment":"ExtractResponse, returned unmodified","anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"Present once the status is `succeeded`. The `/v1/extract` body verbatim, exactly as it was signed."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the extraction failed, in the same words the synchronous path would have used. Failed jobs are never billed."}},"type":"object","required":["job","status","document","submitted_at"],"title":"ExtractJobStatusResponse","description":"One async extraction, mid-flight or finished.\n\n`result` is exactly the body `POST /v1/extract` returned, attestation\nincluded, so anything that consumes one consumes the other unchanged.\n\n**Typed as a raw object on purpose, and this is load-bearing.** A stored\nresult was signed when it was stored, and the signature covers every key in\nit. Declaring this `ExtractResponse` made pydantic re-validate and\nre-serialize the body on the way out, which fills in defaults for every\nfield added to the schema since — so the day `corrections` and `adjusted`\nlanded, every result stored before them came back with keys their signature\nnever covered and stopped verifying. Held results across the whole retention\nwindow went uncertifiable at once.\n\nThe rule the type enforces: a signed body is bytes, not a shape. Never\nreshape one on the way out. `PLACEHOLDER_SHAPE` keeps `/docs` pointing at\nthe shape without letting the model touch the payload."},"ExtractJobSubmitResponse":{"properties":{"job":{"type":"string","title":"Job"},"status":{"type":"string","enum":["queued","running","succeeded","failed"],"title":"Status"},"document":{"type":"string","title":"Document"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"poll_url":{"type":"string","title":"Poll Url","description":"Poll here every few seconds until the status is terminal."}},"type":"object","required":["job","status","document","submitted_at","poll_url"],"title":"ExtractJobSubmitResponse","description":"What `POST /v1/extracts` answers, in milliseconds.\n\nThe extraction has not started yet. Poll `poll_url` until `status` is\n`succeeded` (the whole `/v1/extract` body arrives in `result`) or `failed`\n(the reason arrives in `error`).","examples":[{"document":"citrus_fl_2025014130.pdf","job":"job_9dK2mQxLp7Zv","poll_url":"https://api.easting.ai/v1/extracts/job_9dK2mQxLp7Zv","status":"queued","submitted_at":"2026-08-09T21:14:02Z"}]},"ExtractResponse":{"properties":{"extraction":{"$comment":"DeedExtraction","properties":{"document_kind":{"type":"string","enum":["deed","easement","plat","as_built","other"]},"legal_description_found":{"type":"boolean"},"basis_of_bearings":{"anyOf":[{"type":"string"},{"type":"null"}]},"tracts":{"items":{"properties":{"name":{"type":"string"},"pob_description":{"type":"string"},"calls":{"items":{"properties":{"call_type":{"type":"string","enum":["line","curve"]},"verbatim_text":{"type":"string"},"monument":{"anyOf":[{"type":"string"},{"type":"null"}]},"confidence":{"type":"string","enum":["high","medium","low"]},"bearing":{"anyOf":[{"properties":{"ns":{"type":"string","enum":["N","S"]},"degrees":{"type":"integer"},"minutes":{"type":"integer"},"seconds":{"type":"number"},"ew":{"type":"string","enum":["E","W"]}},"additionalProperties":false,"type":"object","required":["ns","degrees","minutes","seconds","ew"]},{"type":"null"}]},"distance":{"anyOf":[{"type":"number"},{"type":"null"}]},"chord_bearing":{"anyOf":[{"properties":{"ns":{"type":"string","enum":["N","S"]},"degrees":{"type":"integer"},"minutes":{"type":"integer"},"seconds":{"type":"number"},"ew":{"type":"string","enum":["E","W"]}},"additionalProperties":false,"type":"object","required":["ns","degrees","minutes","seconds","ew"]},{"type":"null"}]},"chord_length":{"anyOf":[{"type":"number"},{"type":"null"}]},"radius":{"anyOf":[{"type":"number"},{"type":"null"}]},"curve_direction":{"anyOf":[{"type":"string","enum":["left","right"]},{"type":"null"}]},"arc_length":{"anyOf":[{"type":"number"},{"type":"null"}]},"adjoiner":{"type":"string"}},"additionalProperties":false,"type":"object","required":["call_type","verbatim_text","monument","confidence","bearing","distance","chord_bearing","chord_length","radius","curve_direction","arc_length","adjoiner"]},"type":"array"},"distance_unit":{"type":"string","enum":["feet","inches","yards","miles","rods","poles","perches","chains","links","meters","kilometers","varas","arpents"]},"is_exception":{"type":"boolean"},"tie_calls":{"items":{"type":"string"},"type":"array"},"description_type":{"type":"string","enum":["metes_and_bounds","aliquot","centerline","blanket","exhibit_referenced","facility_relative"]},"stated_area":{"type":"string"},"easement":{"type":"string"},"aliquot":{"type":"string"}},"additionalProperties":false,"type":"object","required":["name","pob_description","calls","distance_unit","is_exception","tie_calls","description_type","aliquot","stated_area","easement"]},"type":"array"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}]},"metadata":{"properties":{"grantors":{"items":{"type":"string"},"type":"array"},"grantees":{"items":{"type":"string"},"type":"array"},"recording":{"type":"string"},"prior_references":{"items":{"type":"string"},"type":"array"},"transfer_date":{"type":"string"},"transfer_date_verbatim":{"type":"string"},"county":{"type":"string"},"state":{"type":"string"},"lot_block":{"items":{"type":"string"},"type":"array"}},"additionalProperties":false,"type":"object","required":["grantors","grantees","recording","prior_references","transfer_date","transfer_date_verbatim","county","state","lot_block"]},"sheet":{"type":"string"}},"additionalProperties":false,"type":"object","required":["document_kind","legal_description_found","basis_of_bearings","tracts","notes","metadata","sheet"],"title":"Extraction","description":"The extracted legal description."},"groundtruth_verdicts":{"items":{"$ref":"#/components/schemas/GroundTruthVerdict"},"type":"array","title":"Groundtruth Verdicts"},"document_verdict":{"anyOf":[{"$ref":"#/components/schemas/DocumentVerdict"},{"type":"null"}],"description":"Present only on documents that produce no tracts (plats, utility as-builts), where the per-tract array is empty. Null on deeds and easements, which speak through `groundtruth_verdicts`."},"usage":{"$ref":"#/components/schemas/TokenUsage"},"model":{"type":"string","title":"Model"},"engine_version":{"type":"string","title":"Engine Version"},"document":{"type":"string","title":"Document","description":"Echo of the document name, if one was sent.","default":""},"input_kind":{"type":"string","title":"Input Kind","description":"How the source arrived: \"document\" for a PDF or image, \"text\" for a pasted legal description. The attestation's SHA-256 is of whichever one it was, and the certificate prints this as its Source line, because a verdict computed from pasted text certifies geometry against what was typed, not against a recorded instrument.","default":"document"},"test_mode":{"type":"boolean","title":"Test Mode","description":"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.","default":false},"corrections":{"items":{"$ref":"#/components/schemas/Correction"},"type":"array","title":"Corrections","description":"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."},"adjustments":{"items":{"$ref":"#/components/schemas/Adjustment"},"type":"array","title":"Adjustments","description":"Compass-rule adjustments applied to this extraction's geometry. The verdicts above judge the recorded description and were NOT recomputed over these."},"attestation":{"anyOf":[{"$ref":"#/components/schemas/Attestation"},{"type":"null"}],"description":"Signature binding this response to the document it was extracted from. Post the response back to `/v1/certificate` to get a GroundTruth Certificate of Digitization; that endpoint verifies this block and refuses a payload that was edited after extraction."}},"type":"object","required":["extraction","groundtruth_verdicts","usage","model","engine_version"],"title":"ExtractResponse","examples":[{"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.0},"call_type":"line","confidence":"high","distance":150.0,"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.0,"vertices":[[0.0,0.0],[149.86,6.54],[0.0,0.0]]},"reasons":[],"status":"PASS","tract":"TRACT 1"}],"model":"claude-opus-5","usage":{"input_tokens":7373,"output_tokens":1140,"seconds":16.4}}]},"GeorefGeometry":{"properties":{"crs":{"type":"string","title":"Crs","description":"Always \"EPSG:4326\" today."},"rings":{"items":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array"},"type":"array","title":"Rings","description":"Exterior ring(s), lon/lat pairs."},"acres":{"type":"number","title":"Acres"},"plss_id":{"type":"string","title":"Plss Id","description":"CadNSDI PLSSID of the resolved township/range."},"source":{"type":"string","title":"Source","description":"Provenance of the fabric, e.g. \"BLM CadNSDI\"."}},"type":"object","required":["crs","rings","acres","plss_id","source"],"title":"GeorefGeometry","description":"Aliquot geometry located on the earth: lon/lat rings from the BLM\nCadNSDI PLSS fabric, quartered by the deed's aliquot chain."},"GroundTruthVerdict":{"properties":{"tract":{"type":"string","title":"Tract"},"status":{"type":"string","enum":["PASS","REVIEW","FAIL"],"title":"Status","description":"PASS: traverse closes and corroborates stated acreage. REVIEW: something disagrees — a human decides. FAIL: the traverse could not be computed."},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons"},"closure_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closure Ratio","description":"Closure denominator as text, e.g. \"1:96,419\", or \"exact\"."},"computed_acres":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Acres"},"computed_sqft":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Sqft","description":"The same area in square feet. Small easements are stated and verified in square feet, where three decimals of acreage is coarser than the tolerance."},"computed_sqm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Sqm","description":"The same area in square meters."},"computed_hectares":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Hectares","description":"The same area in hectares. A document measured in meters states its area in metric, and the verdict compares it in metric."},"distance_unit":{"type":"string","title":"Distance Unit","description":"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.","default":"feet"},"geometry":{"anyOf":[{"$ref":"#/components/schemas/TractGeometry"},{"type":"null"}],"description":"The computed traverse. Null exactly when status is FAIL — a tract that could not be traversed has no geometry to place."},"georef":{"anyOf":[{"$ref":"#/components/schemas/GeorefGeometry"},{"type":"null"}],"description":"Georeferenced geometry, present only on aliquot (PLSS) tracts: the fabric supplies location, so placement needs no POB click. Metes-and-bounds tracts always carry null here."},"location":{"anyOf":[{"$ref":"#/components/schemas/SuggestedLocation"},{"type":"null"}],"description":"A suggested position, present only on a `/v1/locate` response. Always null from `/v1/extract`."},"tie_offset":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Tie Offset","description":"[east_ft, north_ft] from this tract's commencement monument to its point of beginning, walked from the tie courses. Tracts sharing an `anchor` place as a group: one click on the monument, each POB at click + offset. Null when there is no geometry or a tie course could not be interpreted."},"anchor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anchor","description":"Normalized commencement-monument text. Tracts with equal anchors tie to the same point; null means this tract never joins a placement group."},"corrected":{"type":"boolean","title":"Corrected","description":"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.","default":false},"adjusted":{"type":"boolean","title":"Adjusted","description":"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.","default":false}},"type":"object","required":["tract","status"],"title":"GroundTruthVerdict","description":"The verification layer's judgement on one tract.\n\n**Index-aligned with `extraction.tracts`**: entry *i* judges tract *i*, and\nthe arrays are always the same length. Clients depend on that pairing.\n\nThis is the authoritative verdict. A client is not expected to recompute it\n— the rules and tolerances behind it are server-side."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","enum":["ok","degraded"],"title":"Status"},"database":{"type":"string","enum":["ok","unreachable"],"title":"Database"},"engine_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engine Version"}},"type":"object","required":["status","database"],"title":"HealthResponse","examples":[{"database":"ok","engine_version":"0.4.0","status":"ok"}]},"KeyListResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/KeyRow"},"type":"array","title":"Keys"}},"type":"object","required":["keys"],"title":"KeyListResponse"},"KeyRow":{"properties":{"prefix":{"type":"string","title":"Prefix"},"name":{"type":"string","title":"Name"},"monthly_cap":{"type":"integer","title":"Monthly Cap"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"documents":{"type":"integer","title":"Documents"},"input_tokens":{"type":"integer","title":"Input Tokens"},"output_tokens":{"type":"integer","title":"Output Tokens"},"included_docs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Included Docs"},"pool_docs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pool Docs"},"lifetime_documents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lifetime Documents"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"},"grant_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grant Kind"}},"type":"object","required":["prefix","name","monthly_cap","created_at","revoked_at","documents","input_tokens","output_tokens"],"title":"KeyRow","description":"A key plus its month usage, for the admin listing."},"LocateRequest":{"properties":{"extraction":{"additionalProperties":true,"type":"object","title":"Extraction","description":"A `/v1/extract` response, exactly as the API returned it. Its attestation is verified before anything is computed, and any `location` block it carries is discarded rather than trusted."},"source":{"type":"string","title":"Source","description":"Which method to try. \"auto\" tries every implemented source in turn. \"plss_corner\" resolves a commencement corner from the public survey fabric. \"adjoiner\" measures against parcels this account has already placed, which is how a deed that shares a boundary with one you located gets a position of its own.","default":"auto"}},"type":"object","required":["extraction"],"title":"LocateRequest","description":"A request to locate the tracts of an extraction you already hold."},"LocateResponse":{"properties":{"extraction":{"additionalProperties":true,"type":"object","title":"Extraction"},"located":{"type":"integer","title":"Located","description":"How many tracts came back carrying an actual position. Zero is an ordinary answer: most descriptions name no corner the fabric can bind to."}},"type":"object","required":["extraction","located"],"title":"LocateResponse","description":"The posted extraction with `location` blocks filled, re-signed."},"MintResponse":{"properties":{"api_key":{"type":"string","title":"Api Key","description":"Plaintext key. Shown once and never recoverable."},"prefix":{"type":"string","title":"Prefix"},"name":{"type":"string","title":"Name"},"monthly_cap":{"type":"integer","title":"Monthly Cap"},"pool_docs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pool Docs"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["api_key","prefix","name","monthly_cap","created_at"],"title":"MintResponse","examples":[{"api_key":"east_live_AbCdEfGh1JkLmNoPqRsTuVwX","created_at":"2026-07-30T18:24:57Z","monthly_cap":200,"name":"Acme Surveying","prefix":"east_live_AbCdEfGh"}]},"PlacedTract":{"properties":{"document":{"type":"string","title":"Document","description":"The document this tract came from.","default":""},"tract_index":{"type":"integer","minimum":0.0,"title":"Tract Index","description":"Which tract of the document, by its index."},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon","description":"WGS 84 longitude of the point of beginning."},"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat","description":"WGS 84 latitude of the point of beginning."},"rotation_deg":{"type":"number","maximum":360.0,"minimum":-360.0,"title":"Rotation Deg","description":"Clockwise from north, as the placement tool reports it.","default":0.0}},"type":"object","required":["tract_index","lon","lat"],"title":"PlacedTract","description":"One tract's position on the earth, as both the wire and the table carry it.\n\nThe bounds are the point of the model. A longitude of 200 is not a place,\nand a rotation of 4,000 degrees is a client that forgot to take a modulus,\nso both are refused at the edge rather than stored and puzzled over later."},"PlacementListResponse":{"properties":{"reference":{"type":"string","title":"Reference"},"placements":{"items":{"$ref":"#/components/schemas/PlacedTract"},"type":"array","title":"Placements"}},"type":"object","required":["reference","placements"],"title":"PlacementListResponse","description":"Every position this run has, in tract order.\n\nEmpty means the run exists and nothing has been placed yet, which is the\nsame answer a run whose tracts were never put on a map should give.","examples":[{"placements":[{"lat":36.0726,"lon":-80.2442,"rotation_deg":0.0,"tract_index":0}],"reference":"job_1a2b3c"}]},"PlacementRequest":{"properties":{"tracts":{"items":{"$ref":"#/components/schemas/PlacedTract"},"type":"array","maxItems":200,"title":"Tracts","description":"One entry per tract placed, indexed as the extraction indexes them."}},"type":"object","title":"PlacementRequest","description":"Where the operator put each tract of one run.\n\nSent by whichever surface did the placing, and read back by the other. A\ntract left out of `tracts` keeps whatever position it already had, so a\nsurface may save the one parcel somebody just placed without asserting\nanything about the other four in the deed.\n\n**This is not part of the attested extraction and is never folded into\nit.** The signature covers claims the service made and can defend; where a\nparcel sits on the earth is the operator's claim, and nothing verified it.","examples":[{"tracts":[{"lat":36.0726,"lon":-80.2442,"rotation_deg":0.0,"tract_index":0}]}]},"RevokeResponse":{"properties":{"prefix":{"type":"string","title":"Prefix"},"revoked":{"type":"boolean","title":"Revoked"}},"type":"object","required":["prefix","revoked"],"title":"RevokeResponse"},"RunListResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/RunRow"},"type":"array","title":"Runs"}},"type":"object","required":["runs"],"title":"RunListResponse","description":"What this account has run, across every key it owns.\n\nEvery run here is collectable by a signed-in person, whichever of the\naccount's keys ran it. The listing used to name the key the browser\nextracts through, because the collect routes answered for one key and the\npage had to avoid offering a button that would 404; they answer for the\naccount now, so the field is gone rather than left saying nothing.\n\n**Results are kept until you delete them.** Nothing expires on a clock.\n`result_available` reports what the API still holds at the moment you\nasked, and it turns false when you delete a run through\n`DELETE /v1/runs/{reference}` or clear the account with `DELETE /v1/runs`.\nThe record that the work happened outlives the work itself, because an\ninvoice points at it."},"RunRow":{"properties":{"id":{"type":"string","title":"Id"},"document":{"type":"string","title":"Document"},"status":{"type":"string","title":"Status"},"source":{"type":"string","title":"Source"},"reference":{"type":"string","title":"Reference"},"key_prefix":{"type":"string","title":"Key Prefix"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"},"result_available":{"type":"boolean","title":"Result Available","default":false},"document_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Sha256"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"placements":{"type":"integer","title":"Placements","default":0}},"type":"object","required":["id","document","status","source","reference","key_prefix","submitted_at"],"title":"RunRow","description":"One document an account ran, whichever way it was submitted.\n\nA person does not think in tables. They ran a deed on Tuesday and want to\nknow where it went, and whether it came through `/v1/extracts` or inside a\nfolder batch is our filing rather than theirs. So this flattens\n`extract_jobs` and `batch_items`, which already carry the same facts under\ndifferent column names.\n\n`result_available` is the whole point of the row. It reports what the API\nstill holds right now, and a false there is the honest answer to \"where did\nmy result go\": the retention sweep took it, and nothing resurrects it."},"SignupRequest":{"properties":{"email":{"type":"string","title":"Email","description":"Where the account and its invite go"},"company":{"type":"string","maxLength":200,"minLength":1,"title":"Company"},"role":{"type":"string","maxLength":200,"minLength":1,"title":"Role"},"accepted_terms":{"type":"boolean","title":"Accepted Terms","description":"Acceptance of the Terms of Service and Privacy Policy. Checkout used to ask for this explicitly; with no checkout in the path, the signup has to.","default":false}},"type":"object","required":["email","company","role"],"title":"SignupRequest","description":"What someone answers to get three documents without a card.\n\nAll three are required. Company and role are the onboarding half of the\ntrade: the card used to prove intent as well as identity, and asking who\nsomebody is keeps a little of that without asking them to pay first.","examples":[{"accepted_terms":true,"company":"Coates Field Service","email":"dana@coates-field.com","role":"Right of Way Agent"}]},"SignupResponse":{"properties":{"api_key":{"type":"string","title":"Api Key"},"prefix":{"type":"string","title":"Prefix"},"documents":{"type":"integer","title":"Documents","description":"Documents in the allowance, never expiring"},"invited":{"type":"boolean","title":"Invited","description":"Whether a sign-in invitation was mailed for app.easting.ai"}},"type":"object","required":["api_key","prefix","documents","invited"],"title":"SignupResponse","description":"The key, shown once, exactly as the claim page shows one.\n\nThe plaintext travels in this response because the plugin has to work\nwithout a browser round trip. Nothing changes about storage: the digest is\nall the server keeps, and there is no recovery path other than rotating.","examples":[{"api_key":"east_live_AbCdEfGh1JkLmNoPqRsTuVwX","documents":3,"invited":true,"prefix":"east_live_AbCdEfGh"}]},"SuggestedLocation":{"properties":{"crs":{"type":"string","title":"Crs","description":"Always \"EPSG:4326\" today.","default":"EPSG:4326"},"pob":{"anyOf":[{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},{"type":"null"}],"title":"Pob","description":"Suggested point of beginning, lon/lat. **Null whenever the suggestion failed its own checks** — the traverse landed outside the section it claims, the tie chain could not be walked, the fabric had no such section. `reasons` then says which corner was tried and what happened, because a wrong position placed silently is the failure this product exists to refuse."},"rotation_deg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rotation Deg","description":"Rotation the source solved, or null when it solved none. The PLSS corner never solves rotation: it can only assume the bearings are referenced to grid north, and says so in `reasons`."},"source":{"type":"string","title":"Source","description":"Which method produced this: \"plss_corner\" today.","default":""},"basis":{"type":"string","title":"Basis","description":"The monument this was worked from, in words.","default":""},"plss_id":{"type":"string","title":"Plss Id","description":"CadNSDI PLSSID, when one was resolved.","default":""},"status":{"type":"string","title":"Status","default":"REVIEW"},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons"}},"type":"object","title":"SuggestedLocation","description":"Where the server thinks a metes-and-bounds tract sits.\n\nA suggestion, never a result. `status` stays REVIEW even when every check\nagreed, because the operator confirms a position with the same click they\nalready make; the product does not place a parcel on anybody's behalf.\n\nFilled only by `POST /v1/locate`. An `/v1/extract` response always carries\nnull here."},"TokenUsage":{"properties":{"input_tokens":{"type":"integer","title":"Input Tokens"},"output_tokens":{"type":"integer","title":"Output Tokens"},"seconds":{"type":"number","title":"Seconds","description":"Wall-clock seconds spent in the extraction call."}},"type":"object","required":["input_tokens","output_tokens","seconds"],"title":"TokenUsage"},"TractGeometry":{"properties":{"vertices":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Vertices","description":"One point per call endpoint plus the start: N calls give N+1 vertices."},"misclosure":{"type":"number","title":"Misclosure","description":"Distance from the last computed point back to the start."},"perimeter_ft":{"type":"number","title":"Perimeter Ft"},"closure_denominator":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Closure Denominator","description":"The N in a 1:N closure ratio. Null when the traverse closes exactly — JSON cannot represent Infinity; `closure_ratio` says \"exact\"."},"acres":{"type":"number","title":"Acres"}},"type":"object","required":["vertices","misclosure","perimeter_ft","acres"],"title":"TractGeometry","description":"The computed traverse, in its own local plane: POB at the origin, feet.\n\nEverything a client does with a placed tract — previewing it under the\ncursor, writing it to a layer — is an affine transform of these vertices, so\nreturning them is what lets a client be fully interactive without carrying\nthe traverse math itself."},"UploadRequest":{"properties":{"bytes":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Bytes","description":"The document's exact size. Optional, but supplying it signs the length into the URL so S3 refuses a body of any other size, which is where the limit is cheapest to enforce."}},"type":"object","title":"UploadRequest","examples":[{"bytes":12400000}]},"UploadResponse":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"Pass this to /v1/extract as `X-Upload-Id`."},"put_url":{"type":"string","title":"Put Url","description":"PUT the PDF here. Single use, and it expires."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds the URL stays valid."},"max_bytes":{"type":"integer","title":"Max Bytes"}},"type":"object","required":["upload_id","put_url","expires_in","max_bytes"],"title":"UploadResponse","description":"A one-shot slot to PUT a document into.\n\nThe document goes straight to storage rather than through this API, which\nis what lifts the size ceiling: a Lambda Function URL base64-encodes request\nbodies and caps them near 4.5 MB. Extraction then takes the `upload_id` and\nthe object is deleted as it is read.","examples":[{"expires_in":900,"max_bytes":33554432,"put_url":"https://easting-staging.s3.amazonaws.com/staging/...","upload_id":"upl_9dK2mQxLp7ZvR4tYbN6w"}]},"UsageResponse":{"properties":{"key":{"type":"string","title":"Key"},"name":{"type":"string","title":"Name"},"monthly_cap":{"type":"integer","title":"Monthly Cap","description":"Hard ceiling. For a subscribed key this is above `included_docs` and exists to catch runaway usage, not to be reached."},"included_docs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Included Docs","description":"Documents the subscription includes each month. Null on a key billed by arrangement rather than by subscription, where `monthly_cap` is the quota itself."},"overage_documents":{"type":"integer","title":"Overage Documents","description":"Documents this month beyond `included_docs`, billed per document.","default":0},"documents":{"type":"integer","title":"Documents"},"input_tokens":{"type":"integer","title":"Input Tokens"},"output_tokens":{"type":"integer","title":"Output Tokens"},"period_start":{"type":"string","format":"date-time","title":"Period Start"},"period_end":{"type":"string","format":"date-time","title":"Period End"}},"type":"object","required":["key","name","monthly_cap","documents","input_tokens","output_tokens","period_start","period_end"],"title":"UsageResponse","examples":[{"documents":47,"included_docs":100,"input_tokens":341902,"key":"east_live_AbCdEfGh","monthly_cap":300,"name":"Acme Surveying","output_tokens":58311,"overage_documents":0,"period_end":"2026-08-01","period_start":"2026-07-01"}]},"UserKeyRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"What this credential is for. Shown in your key list and nowhere else."}},"type":"object","required":["name"],"title":"UserKeyRequest","description":"Minting a key for yourself. No caps here on purpose: a key is a\ncredential against an entitlement you already have, so the plan's numbers\nare copied rather than chosen, and a second key never buys a second\nallowance.","examples":[{"name":"Nightly ETL"}]},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WhoAmIResponse":{"properties":{"email":{"type":"string","title":"Email"},"superadmin":{"type":"boolean","title":"Superadmin","description":"Easting staff. Reaches the product without a subscription."},"stripe_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Customer Id","description":"Null for staff, who have no billing relationship."},"active_keys":{"type":"integer","title":"Active Keys"}},"type":"object","required":["email","superadmin","active_keys"],"title":"WhoAmIResponse","description":"Who a Cognito token belongs to, as the portal needs it on first paint."}},"securitySchemes":{"EastingApiKey":{"type":"http","description":"Your `east_live_...` key.","scheme":"bearer"}}}}