Examples: correct a PDF end to end
Each example corrects one document from start to finish: upload it, state your authority to correct it, propose the correction, apply it, check the verification and download the result. Each reads three environment variables:
| Variable | What it is |
|---|---|
CECO_URL | The address of this service, such as https://ceco.example |
CECO_API_KEY | Your key, ceco_live_… |
CECO_PDF | The PDF to correct |
The correction is the same in all three: the first "tije" on the first page becomes "tij". Change find and replacement for your own documents. Every example on this page is run against a real server by CECO's test suite, so what you read here works.
curl
The same as the Quickstart, for a shell script.
set -e
AUTH="Authorization: Bearer $CECO_API_KEY"
JSON="Content-Type: application/json"
field() { grep -o "\"$1\":\"[^\"]*\"" | head -n 1 | cut -d'"' -f4; }
DOC=$(curl -sf -H "$AUTH" -F "file=@$CECO_PDF;type=application/pdf" \
"$CECO_URL/v1/documents" | field document_id)
ATT=$(curl -sf -H "$AUTH" -H "$JSON" -d "{\"document_id\":\"$DOC\"}" \
"$CECO_URL/v1/attestations" | field attestation_id)
REQUEST="{\"page_index\":0,\"target\":{\"method\":\"text_layer\",\"find\":\"tije\",\"match_index\":0},\"replacement\":\"tij\",\"attestation_id\":\"$ATT\"}"
PROPOSAL=$(curl -sf -H "$AUTH" -H "$JSON" -d "$REQUEST" \
"$CECO_URL/v1/documents/$DOC/edits:propose")
echo "$PROPOSAL" | grep -q '"refusal":null' || { echo "refused: $PROPOSAL"; exit 1; }
curl -sf -H "$AUTH" -H "$JSON" -o /dev/null \
-d "{\"request\":$REQUEST,\"apply\":{\"proposal_id\":\"$(echo "$PROPOSAL" | field proposal_id)\",\"confirm_token\":\"$(echo "$PROPOSAL" | field confirm_token)\"}}" \
"$CECO_URL/v1/documents/$DOC/edits"
PASSED=$(curl -sf -H "$AUTH" "$CECO_URL/v1/documents/$DOC/verification" \
| grep -o '"passed":[a-z]*' | head -n 1)
[ "$PASSED" = '"passed":true' ] || { echo "not verified"; exit 1; }
curl -sf -H "$AUTH" -o corrected.pdf "$CECO_URL/v1/documents/$DOC/export/download"
echo "corrected.pdf written for $DOC"
Python (httpx)
import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['CECO_API_KEY']}"}
with httpx.Client(base_url=os.environ["CECO_URL"], headers=headers, timeout=120) as ceco:
with open(os.environ["CECO_PDF"], "rb") as pdf:
uploaded = ceco.post(
"/v1/documents", files={"file": ("invoice.pdf", pdf, "application/pdf")}
).raise_for_status().json()
document_id = uploaded["document_id"]
# Every correction carries a statement that you are authorised to make it.
attestation = ceco.post(
"/v1/attestations", json={"document_id": document_id}
).raise_for_status().json()
request = {
"page_index": 0,
"target": {"method": "text_layer", "find": "tije", "match_index": 0},
"replacement": "tij",
"attestation_id": attestation["attestation_id"],
}
proposal = ceco.post(
f"/v1/documents/{document_id}/edits:propose", json=request
).raise_for_status().json()
if proposal["refusal"]:
# `advice` says why, and what would change the answer.
raise SystemExit(f"refused: {proposal['advice'] or proposal['refusal']}")
ceco.post(f"/v1/documents/{document_id}/edits", json={
"request": request,
"apply": {
"proposal_id": proposal["proposal_id"],
"confirm_token": proposal["confirm_token"],
},
}).raise_for_status()
verification = ceco.get(
f"/v1/documents/{document_id}/verification"
).raise_for_status().json()
if not verification["passed"]:
raise SystemExit(f"not verified: {verification['failure_reason']}")
corrected = ceco.get(f"/v1/documents/{document_id}/export/download").raise_for_status()
with open("corrected.pdf", "wb") as out:
out.write(corrected.content)
print("corrected.pdf written for", document_id)
Waiting out a limit
429 too_many_requests and 503 busy both carry Retry-After, and a request answered with either did nothing, so it is safe to send again:
import os
import time
import httpx
def call(client: httpx.Client, method: str, url: str, **kwargs) -> httpx.Response:
"""Send a request, waiting out a rate limit or a busy engine."""
for _ in range(5):
response = client.request(method, url, **kwargs)
if response.status_code not in (429, 503):
return response
time.sleep(int(response.headers.get("Retry-After", "1")))
return response
headers = {"Authorization": f"Bearer {os.environ['CECO_API_KEY']}"}
with httpx.Client(base_url=os.environ["CECO_URL"], headers=headers, timeout=60) as ceco:
usage = call(ceco, "GET", "/v1/usage").raise_for_status()
print(usage.headers["X-RateLimit-Remaining"], "of", usage.headers["X-RateLimit-Limit"],
"requests a minute left right now")
Node (fetch)
Node 18 or later, saved as an ES module and run with node correct.mjs.
import { readFileSync, writeFileSync } from "node:fs";
const base = process.env.CECO_URL;
const auth = { Authorization: `Bearer ${process.env.CECO_API_KEY}` };
async function ceco(method, path, body) {
const init = { method, headers: { ...auth } };
if (body instanceof FormData) {
init.body = body;
} else if (body !== undefined) {
init.headers["Content-Type"] = "application/json";
init.body = JSON.stringify(body);
}
const response = await fetch(base + path, init);
if (!response.ok) {
throw new Error(`${method} ${path}: ${response.status} ${await response.text()}`);
}
return response;
}
const form = new FormData();
const pdf = new Blob([readFileSync(process.env.CECO_PDF)], { type: "application/pdf" });
form.append("file", pdf, "invoice.pdf");
const { document_id } = await (await ceco("POST", "/v1/documents", form)).json();
// Every correction carries a statement that you are authorised to make it.
const { attestation_id } = await (
await ceco("POST", "/v1/attestations", { document_id })
).json();
const request = {
page_index: 0,
target: { method: "text_layer", find: "tije", match_index: 0 },
replacement: "tij",
attestation_id,
};
const proposal = await (
await ceco("POST", `/v1/documents/${document_id}/edits:propose`, request)
).json();
if (proposal.refusal) throw new Error(`refused: ${proposal.refusal}`);
await ceco("POST", `/v1/documents/${document_id}/edits`, {
request,
apply: { proposal_id: proposal.proposal_id, confirm_token: proposal.confirm_token },
});
const verification = await (
await ceco("GET", `/v1/documents/${document_id}/verification`)
).json();
if (!verification.passed) throw new Error(`not verified: ${verification.failure_reason}`);
const corrected = await ceco("GET", `/v1/documents/${document_id}/export/download`);
writeFileSync("corrected.pdf", Buffer.from(await corrected.arrayBuffer()));
console.log(`corrected.pdf written for ${document_id}`);