Changelog and the versioning promise
The versioning promise
Everything under /v1 changes additively only: new routes, new optional request fields and new response fields may appear, and nothing you rely on is renamed, removed or made to mean something else. Read responses so that a field you do not know is ignored. A change that could break an integration would be a new version, /v2, announced here with the date /v1 stops.
5 November 2026
- The light editor, as steps on a document rather than corrections of it:
POST /v1/documents/{document_id}/overlayslays text boxes, images, shapes, freehand strokes, highlights, underlines, strike-throughs and notes over the pages as real, removable PDF annotations;…/overlays:removetakes them off by name;…/cropmoves the crop box of the pages named, or puts it back withrestore, discarding nothing;…/flattenbakes annotations and form fields into the pages for a final copy;…/repairrebuilds a damaged file and says inreportwhat was found and what could not be recovered. Each answers the document, the newrevision_id, adescriptionin one sentence, the engine'srecordand averificationofkind: "editor"withcontent_streams_unchanged(true for every step but a flatten) andcarries_corrections. None of them changes the page's own ink: an opaque box, text box or image over existing words is refused,ink_edit_refused, and pointed at a correction, which changes the word in place. None is charged, and a document with only overlays on it downloads free. New codes:ink_edit_refused,off_page,too_many_overlays,image_unreadable,text_too_long,font_cannot_draw,signed_document,protected_feature,unknown_overlay,bad_request,nothing_to_flatten,nothing_to_uncrop,nothing_to_repairandeditor_failed;image_too_largeis also answered here, for an overlay image over 2 MB, andunreadable_pdffor a stored file damaged past reading. download_termson every document (GET /v1/documents/{document_id}and wherever a document is answered): whether downloading it costs nothing, and thereason--nothing_changed,overlays_only,made_free,empty_fillable,correctionsorredaction-- as the download route will then decide it.- Undo and redo step over whatever is at the head: a correction or an editing step, one history.
undonein the answer toPOST /v1/documents/{document_id}/undoand…/redois still the correction when a correction was moved over; when a step was, it carrieskind(editor),operation,seq,page_index,pages,verifiedandcreated_atinstead. Readkindbefore reading a correction's fields. Stepping over an editing step uses none of the day's allowance, as laying it used none; stepping over a correction or a redaction still uses half a correction. - A damaged file -- a broken cross-reference table, a truncated stream -- is uploaded like any other:
POST /v1/documentstakes it, rebuilds the table to read it, and lists the concern on the document;…/repairthen rebuilds the file. Only a file no repair could read answersunreadable_pdf, as before. - A certificate's statement lists the editing steps and the redactions standing on the document in
assembled, each withoperationeditor(and itsstep) orredaction, itspages,countsand adescription: "1 highlight and 1 text box laid over page 1", "Page 1 cropped", "2 regions on page 1 redacted with a black mark". Counts, page numbers and the mark only: never what a text box says, what was removed, or a label's words. A correction whose place a later redaction covers is still counted and placed, but its words are withheld: the certificate says they were redacted rather than repeating them. - Redaction.
POST /v1/documents/{document_id}/redactions:findanswers where a word or phrase (query, at least three characters), a pattern (pattern:email,phone,iban,card,dateornumber) or a bounded expression (regex) appears: each match with itsmatch_id, itspage_index, itsboxes_renderat therender_dpiasked for, and thetextit matched, which goes to the document's owner and nowhere else (an assistant connected over MCP is answered without it). Pages that are pictures are read with the recogniser where the server has one (scanned_pages_read), and counted inscanned_pages_unreadwhere it has not. At most 500 matches are listed;totalandtruncatedsay when there were more. A scanned page read once is remembered, and one find reads at most five afresh (too_many_scanned_pagespast that: find in parts withpages).elsewherecounts where else in the file what was asked for stands -- metadata, bookmarks, notes, form fields, links, attachments, scripts, alternative text -- each place a number, never the words, and an assistant is told the counts too. Not charged, and nothing is changed.POST /v1/documents/{document_id}/redactionsremoves the content ofregions(each apage_index, abox_renderand itsrender_dpi; at most 200 places) from the file for good -- text, image pixels and drawings, in every layer -- draws themark(black,white, orlabelwith alabelof at most 40 characters) where it was, and checks that none of it survives before anything is kept.match_idsnames the find's matches the regions make good on: a region that is a match's box is redacted at the match's own box, once, however many times it is sent; ids from before the document changed are refused,match_stale(409). There is no redact-all. The answer is a new revision with the head as its parent, itsrecord(pages from one, boxes in the page's own unrotated points, counts and the mark; never the content), theremovedcounts, averificationofkindredactionwithremoved_verified, each check inprobes,passed,outside_pixelsandcarries_corrections, and inrefusedany region declined because it holds a watermark, a signature, a seal or a security feature, with the engine's advice. A redaction is not a correction: it is not in the list of corrections, and its manifest carries it underredactions. One correction of the daily allowance per request, nothing on a refusal; downloading a redacted document is priced as a corrected one (download_terms.reasonredaction) until the owner names the plans that hold redaction. One redaction changes at most ten pages (too_many_pages_to_redact), and is held to the account's memory ceiling as a page operation is (page_op_memory_ceiling). The answer also says, instill_elsewhere, where the words of the matches redacted still stand outside the pages' text, as counts, and inshares_endedhow many links to a correction it closed: a link whose picture showed part of what was redacted stops opening, and opens again only if the redaction is undone. New codes:query_too_short,pattern_unknown,regex_invalid,ocr_unavailable,redaction_invalid,region_off_page,too_many_regions,label_too_long,label_cannot_draw,match_stale,nothing_redacted,removal_failed,removal_not_verified,too_many_pages_to_redact,too_many_scanned_pagesandredaction_failed;protected_featureandsigned_documentare answered here too.
1 November 2026
- An MCP server for assistants, beside the API: Claude, ChatGPT or another MCP client connects to a person's own account, the person signs in on CECO PDF, and the assistant works on their documents through the same routes and checks as the site. It is described on its own page. Nothing a key can call changed: the routes it adds under
/v1/mcp/,/v1/oauth/and/v1/me/connectionsserve the person's own pages, refuse a key withnot_available_to_api_keys, and are not in the reference.
22 October 2026
GET /v1/documents/{document_id}/pages/{page_index}/fields: where the answers go on a page. Every field found -- from existing form fields, rules, boxes, combs, underscores and checkbox characters, and on scans from the ink -- with its kind, its rectangle in points and in pixels atrender_dpi, and whether the finder issureor it ispossible.refusalexplains in words a page that cannot be filled:issued_document,xfa_formorno_fields. Not charged.POST /v1/documents/{document_id}/fill: fill a form in one request.outputfinishedsets the answers on the page in type matching the form;fillableadds real form fields. The answer is a new revision with its verification, like a correction, and a field that could not be filled is listed inrefusedwith its reason. One correction per saved filled or signed copy, regardless of field or page count. An empty fillable copy uses no daily allowance; any paid corrections already in its source keep their export gate. Answers are written into the file and are never logged or audited.- Self-signing uses the same save: a signature field accepts a
signaturecontaining the person's own typed, drawn or uploaded mark and explicitown_markconfirmation. Marks are placed only on blank areas and verified at 300 DPI with zero outside pixels changed. Existing ink, issued documents, XFA and files marked as digitally signed are refused. The manifest records mode, hash, placement and time; the audit records counts and regions, never the mark contents.
9 October 2026
- Converting into PDF.
POST /v1/converttakes one multipart part namedfilesfor each file -- photos (JPEG, PNG, TIFF, HEIC, WebP, AVIF), plain text, Markdown, web pages and Office files (Word, Excel, PowerPoint and their OpenDocument kinds) -- in the order their pages should come, with optionalpaper(a4,letter, orfit: each page the size of its picture),orientation(auto,portrait,landscape),margin_mm(0 to 50) andmarkdown(trueorfalse, to read a text file as Markdown or not; left out, a name ending.mdor.markdownis Markdown). Each file is held to the plan's upload limit, and all of them together to 32 MB. It answers 202 with ajob_id, andGET /v1/convert/{job_id}answersqueuedorrunningwith itsprogress, thendonewith the new document indocument_idanddocument, adescriptionin words, thepages, the engine'srecord, andsources: each file in the order sent, named by its place (file-2.jpg, never the name it was sent with), the kind it was recognised as from its bytes, its SHA-256 and size, its pages, itsfidelity(bytes_kept,pixels_kept,reencoded,typeset,layout_keptorlayout_may_differ) andflagssaying why; orrefusedwith the reason inrefusal. Office files are drawn by LibreOffice in a converter with no network, stopped at its time limit; a server with none refuses them in words. The new document is downloaded through the export route, free until a correction is made on it: nothing in it was corrected here. Its manifest records the conversion, and its verification iskind: "convert". An identity document is refused and deleted, as at upload. Another account's conversion is answered 404, as an id never issued. New codes:convert_invalid,convert_too_large,convert_unsupported,convert_unreadable,convert_characters_missing,convert_runaway_layout,convert_password_needed,convert_timed_out,convert_office_unavailableandconvert_failed. GET /v1/documents/{document_id}/verificationanswered 500 for a document made by a page operation (merging, splitting, rotating and the rest), by marks (a watermark, page numbers) or by reading a scan, whose pages have no difference to count. Each such page'shistogramis now{}, as for any page with no difference.- The errors page had
not_a_pdfas 422; it is answered with 415, as it always was.
5 October 2026
- A correction to a scan is attacked by the engine's own forensic detectors before it is handed over, and the verification says what they found:
self_attack, a new optional field on the answer toPOST /v1/documents/{document_id}/editsand onGET /v1/documents/{document_id}/verification. It holds theoutcome(clean,weaknessornot_checked); thekindof page it was judged as, read from the file (how the page image is stored and at what resolution, such asjpeg@300), and whether the check has been measured on that kind (covered); the kind ofchangeit judged (replace,deleteorinsert); thereasonit was not checked, as a code; thedetectorthat found the most, itsscoreand, for a weakness, itsstrength; the twolinesthe score is held to (weaknessandrefusal), measured for that kind of page and that kind of change, because words added to blank paper stand out to the detectors more than words written over others even when neither left a trace; how manyattemptswere made, each onetriedwith its own outcome and score; and theversionof the check. A correction delivered with a weakness still passed:passedandfailure_reasondo not change, nor do export, certificates and shared links.self_attackis null for a correction verified before today and for a document made by a page operation, a compression or marks. Null never means clean. - The engine's whole record of the check is also in a proposal's
measurementsand in the manifest's edit, undercalibration, asself_attack; the audit trail'sedit.applyentry carries itsoutcome,kind,change,detector,score,attempts, each attempttriedandversion. - A correction the detectors find far more readily than the engine's own corrections of that kind on that kind of page is made again another way, and refused if no attempt comes back under the refusal line: the code is
self_attack_refused, and an apply answersedit_refused. Nothing is written to the document or its audit trail for a refused correction. Where too few of the engine's corrections of a kind were measured to set a refusal line,lines.refusalis null and that kind of change is never refused: a score above the weakness line is stated as a weakness. - Every correction has a quality score:
quality, a new optional field on each correction inGET /v1/documents/{document_id}/correctionsand in the answers toPOST /v1/documents/{document_id}/undoandPOST /v1/documents/{document_id}/redo, and on the answer toPOST /v1/documents/{document_id}/editsandGET /v1/documents/{document_id}/verification. It holds the score,level(good,fair,weakorunknown: the lowest of the parts that apply), aheadlinesentence, fourcomponents(letters,paper,fileandcheck, each with alevel, acode, a plainsentenceand, for the check,whereit looked and itsdetectororreason; the check isnot_applicableto a change made in the document's text),advice(codes and sentences, only where something measured on the page supports them) and theversionof the scoring. It is worked out each time it is read, from what was recorded when the correction was made, and stored nowhere, so corrections made before today are scored too: a part their record does not cover, such as the check on a correction to a scan, isnot_recorded, and the scoreunknown. Null for a correction whose record holds no measurements. Null never means good. See the verification model. - Reading a scan.
POST /v1/documents/{document_id}/ocrtakeslang(autowhen left out, or a language from the newGET /v1/ocr/languages, such assqi; two or three may be joined,eng+sqi) andoutputs(any ofpdf,text,layoutandcsv; all four when left out), and answers 202 with ajob_id. A request with no body at all reads with both defaults.GET /v1/ocr/{job_id}answers with itsprogress, then each page'sstate(read,partial,unreadableorskipped), withskipped_becauseandreason(codes),source(where its words came from:ocr, read here;text_layer, the file's own text, its real text or a searchable layer another program wrote, quoted as it stands and never read or checked here; ornone),lang,lang_confidence(the language detector's score),mean_confidence(the recogniser's own mean score for the words read, which is not how likely they are to be right:stateis the verdict),words_written,words_withheldandnote(what the text export says of the page, null when no text was made); the searchable PDF, a new document, indocuments; where each text output is, indownloads;pdf_refusalandcsv_refusalwhen one output could not be made while the others were; the engine'srecordof the text layer; andrefusalwhen nothing could be made. Withautothe script is found first, then the best of a short list of languages written in it; the language a page was read in is on every page. The document read is left exactly as it was. - The searchable PDF carries every stream of the file read byte for byte, with a text layer added after each scanned page's own content that draws no ink, so its pictures and its pixels are unchanged; that is checked before it is kept. A page that has its own text is given no layer. A word read with too little confidence is left out of the layer and the text rather than guessed, and a page nothing on which could be read with confidence is given no words at all. Its manifest records the text layer, its certificate says how the file was made, and its verification is
kind: "ocr". - The text downloads are
GET /v1/ocr/{job_id}/text.txt(the words in reading order),layout.txt(laid out as each page has them) andtable.csv(the pages that are clearly tables; refused withocr_not_tabularwhen none is). Each takes what downloading the document takes,can_export: a signed-in editor or viewer is refused withmissing_capability. Each is free when the version read holds no correction made here -- a file merged, split, turned or Bates-numbered here is traced back to the files it was made from, as each was when it was read, to say so -- and otherwise paid for as the corrected document is (payment_required, oremail_requiredbefore payments start), as is a file that can no longer be traced to them.carries_correctionssays whether, andcorrectionswhy:uncorrected,correctedoruntraced.DELETE /v1/ocr/{job_id}deletes a reading and its text; deleting the document read deletes its readings too. New codes:ocr_language_unknown,ocr_not_ready,ocr_not_tabular,ocr_unreadable,ocr_nothing_to_read,ocr_too_many_pagesandocr_failed. - A correction made later to a page of a searchable copy takes the words under the corrected area out of its text layer, so a search no longer finds the old word there, and the correction's record says it did. The record the corrected file carries keeps the copy's
text_layersentry, with the words taken out counted in itstrims(the page, how many went and stayed, and the correction'sedit_id; never which words), and keeps themarksof a watermarked or numbered file too.
30 September 2026
- Comparing.
POST /v1/documents/comparetakesbefore_document_idandafter_document_id, and optionallybefore_versionandafter_version(current, the default: the document as it stands; ororiginal: as it was uploaded), and answers202with a comparison and itsjob_id; the versions are pinned then.GET /v1/compare/{job_id}answers itsstatus(pending,running,done,refused,failed),progressandstage, and once done itsrecord:pairs(page numbers from one, a pair'sstatusidentical,changed,insertedorremoved, itsregionswith aboxin pixels at the record'sdpi), the numberedchanges(kind,box, thetextthey show, asummary), thetextthat changed (before,after, where, a few words of context,sourcetextorocr), thepropertiesthat differ,notes, asummaryof counts and adescription. Pixels count as changed at any difference (tolerance0).changed_sincenames a side whose document has been corrected or undone since. A refusal is inrefusal:compare_too_many_pages,page_too_large,source_not_verified,not_found. GET /v1/compare/{job_id}/report.pdfandGET /v1/compare/{job_id}/changes.csv: the comparison as a report of outlines and words, and its changed words one a row, whose cells never start a spreadsheet formula. For a subscription, or a pack bought in the last thirty days (402 compare_report_paid;402 email_requiredbefore a deployment takes payments);409 compare_not_readyuntil it is done.DELETE /v1/compare/{job_id}deletes one now, and each is deleted with either of its documents. Twenty comparisons may be asked for in ten minutes per caller.- Protecting.
POST /v1/protecttakes a PDF as multipartfile, withuser_password(to open it),owner_password(for its permissions),permissions(all,none, or any ofprint,print_high_quality,copy,edit,annotate,fill_forms,assemble, comma separated) andpasswordfor a file that already has one, and answers the file protected with AES-256 (PDF 2.0,/V 5 /R 6). Nothing is stored. The file is read back before it is returned: it does not open without its password, the permissions are those asked, and it decrypts to the file protected, object for object. Two protections of the same file are different bytes -- AES draws fresh key material each time -- and decrypt to the same content. Refusals:protect_invalid(no password, restrictions with no owner's password or with the opening one, a password over 40 characters or one readers read differently),protect_password_needed,protect_password_wrong,protect_owner_password_needed. - Unlocking.
POST /v1/unlocktakesfileandpassword-- the owner's, for a file whose owner restricted it -- and answers the file without protection. One password, tried once; ten tries in ten minutes per caller.protect_not_protectedfor a file with nothing to take off. - Marks.
POST /v1/documents/{document_id}/watermark(textorimage_base64,opacity,rotation,position,sizeorscale,face,color,pages),POST /v1/documents/{document_id}/page-numbers(stamps,pages,start) andPOST /v1/documents/bates(document_ids,prefix,start,digits,suffix,position) each answer201with new documents, like a page operation; the documents marked are left as they were. Each result's verification report haskindmarks, and its manifest amarksentry saying what was drawn, where, and for Bates each file'sfirst,lastandnextnumber. Refusals:marks_invalid,marks_glyph_missing,marks_image_unreadable,marks_would_cover,marks_do_not_fit. - A correction that would take away part of a watermark or a Bates number is refused: a proposal's
advice.codeiswatermark_removal_refused, and an apply answersedit_refusedwith the same evidence. - A manifest may now carry
marksandprotections. Likepage_operationsandcompressions, each is left out of the hashed form while it is empty, so no manifest issued before today changes its hash.
25 September 2026
- Compressing.
POST /v1/compresstakes a PDF as multipartfile(andpassword, used once), up to 100 MB for every plan (413 compress_too_largeover it, withsize_mbandlimit_mb);POST /v1/documents/{document_id}/compresstakes a document as it stands. Both answer202with ajob_id.GET /v1/compress/{job_id}then saysstatus(pending,running,ready,choosing,done,failedorexpired),progressandstagewhile it works, and onceready:original(bytes,size,pages,budget,summary), up to fouroptions— each withbytes, the exact length of the file you get if you choose it,size,smaller,saving,gives_up,settingand apreview_url— andpreview, the same small region of the densest text at 100% zoom from the file (original_url). When there is nothing worth offering,refusalsays why in words, with where the bytes are.presetsare the sizes the interface offers, the two mail limits among them. POST /v1/compress/{job_id}/underwithmax_bytes: the best-looking file under it that still reads, as option 5 inunder, orunder.refusal(compress_over_limit) with the smallest readable size and the offer to split.POST /v1/compress/{job_id}/choosewithoptionmakes it a document (documents,statusdone), downloaded with the export route. Its one revision is verified by every page being checked still readable in the file as written (verification reportkindcompress), and its manifest gainscompressions: which setting, each image's resolution and quality or why it was left, the sizes before and after, the pages recompressed, and which document and revision it was made from, or the upload's SHA-256. The file carries no manifest inside it, so that its size is the size you chose; the manifest is atGET /v1/documents/{document_id}/manifestas always. A document compressed from a corrected one does not carry its verification or certificate (corrected_sourcesays so first); a certificate issued for it later, once it has corrections of its own, says how the file was made. Charged like a page operation. Downloading it is free when the file compressed carried no corrections, as downloading an upload is; one made from a corrected document is paid for like any corrected file (402 payment_required). Its verification report says which:carries_corrections.POST /v1/compress/{job_id}/splitwithmax_bytesandcompress: documents each under the size, of the smallest readable option or of the file as it was.DELETE /v1/compress/{job_id}deletes the options now. Otherwise they are deleted an hour after they were asked for, or when a document is made from one;410 compress_expiredafter that. New codes:compress_too_large,compress_expired,compress_busy,compress_not_ready,compress_no_such_option,compress_failed,compress_unverified, and the refusals inside the answer (Errors).POST /v1/documents/mergeandPOST /v1/documents/{document_id}/pages: merge documents, and split, extract, remove, reorder or rotate pages. Each makes new documents (answered asdocuments, with the operation'sdescriptionandrecord) and leaves its sources as they were. Every page is carried as the stored bytes of the page it came from, and that is checked before the answer; the new document passes the export gate like any verified one, and its manifest gainspage_operations, which says which pages of which documents it holds, in what order and turned how far. New codes:page_op_out_of_range,page_op_invalid,page_op_part_too_large,page_op_memory_ceiling,source_not_verified,page_operation_failed.- A certificate for a document a page operation made carries
assembled: what was done and the SHA-256 of each file the pages came from. document_classon every document, in the answers ofPOST /v1/documents,GET /v1/documentsandGET /v1/documents/{document_id}: what kind of document it is, read from its first three pages when it arrived, with every page looked at for an identity document.classis one ofletter,report,invoice,statement,form,table,contract,certificate,screen_photo,identityorunknown, with aconfidencefrom 0 to 1;identitynames which identity document it looks like (passport,id_cardordriving_licence) with its own confidence, or is null;decisionis what the service did about it:nonefor every ordinary class, otherwiserefused,warnedorallowedas the deployment is set. Residence permits, diplomas and professional certificates are recognised and accepted like any other document, whatever the deployment's setting: a diploma or a professional certificate is recorded as classcertificate, a residence permit as the ordinary class it reads as, and for all threeidentityis null anddecisionisnone.identity_pageslists the pages that look like one, counted from zero, each with its kind.pages_read,pages_examinedandocr_usedsay how it was read. Null for a document classified before this date until it is re-examined.identity_document_refused(422) fromPOST /v1/documents, and fromPOST /v1/documents/{document_id}/classifyfor a caller who could delete the document: the service declines identity documents by default — the class above — and deletes the file of a document detected as one, with everything made from it. The answer carriesidentity_kind,confidenceandreference; the record keeps the file's hash, size, page count and page dimensions, the class with its signals, and the decision. A re-classify by anyone else keeps the document and adds a warning to itsconcerns. Additive: a document that is not detected as one is handled exactly as before. See Errors.
24 September 2026
revision_idon every certificate, in the answers ofPOST /v1/documents/{document_id}/certificateandGET /v1/documents/{document_id}/certificates: the revision it certifies. While it equals the document'shead_revision_id, the certificate covers the document as it now stands; a correction made or undone since moves the head and leaves this where it was.declared_renderanddeclared_dpion each correction, inGET /v1/documents/{document_id}/correctionsand the answers to undo and redo: the region verification held the correction to, in pixels at that resolution. It can be larger thanbox_render, the edit's own box: a text edit declares the rest of its line, because the line reflows, and a correction on a JPEG scan can declare whole compression blocks. Null for a correction verified before this date.
15 September 2026
turnonPOST /v1/documents/{document_id}/pages/{page_index}/ocrandPOST /v1/documents/{document_id}/pages/{page_index}/preflight: 0, 90, 180 or 270, clockwise. The page is read turned that way, as a viewer shows a page that was saved sideways, and every box, in the request and in the answer, stays in the page's own pixels. Optional: without it, or at 0, a page is read as its file has it, as before.
v1 — 12 September 2026
The first published version of the API.
- API keys for Business accounts, with a lifetime of 7, 30 or 90 days, or none.
- Keys stop immediately when revoked, and permanently when the Business plan ends.
- One list of the routes a key may call; everything else refuses a key with
403 not_available_to_api_keys. - A rate limit per key, with
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reseton every response. - Verification certificates, and the public routes that check them.
Last updated