PDF/A for archiving
With pdf.pdfa, a render is a PDF/A file (ISO 19005), the format for documents that have to open the same way in years to come: every font is inside the file, colours are defined by a profile, and nothing depends on the network or a password. Formfeed converts the finished PDF to the level you ask for, PDF/A-2b or PDF/A-3b, checks the file against the rules of that level before it is delivered, and reports what the check said.
PDF/A is a file format. Whether you keep a document as a law asks of you is a question of how your archive is run, not of the file. What the format does not settle says where that line runs.
What you get
- A PDF/A of the level you choose.
"2b"or"3b", on every PDF the template renders or on one request. - Checked before delivery. The converted file is held against the rules of its level by veraPDF, the open-source validator of the PDF Association and the Open Preservation Foundation. The file you receive is the file that was checked.
- The claim only with the proof. A file that fails is not delivered, and no ordinary PDF is delivered in its place: the render fails and names the rules. There is no mode that delivers a file that was not checked or did not pass.
- A report on every render. The render's
pdfafield names the level, the validator and its version, and the hash of the file it judged. - Every plan, no extra unit.
It works for the PDF output of HTML templates, of ad-hoc renders with html or url, and of Word and PowerPoint templates, in single renders and in batches, synchronous and asynchronous.
Which level
| Level | What it allows | Choose it when |
|---|---|---|
PDF/A-2b | Everything a rendered document needs, transparency included. Files attached to it must be PDF/A too. | The archive or the recipient asks for PDF/A-2, or you want no attachments in your archive. |
PDF/A-3b | The same, and any file attached to the PDF. An e-invoice is a PDF/A-3b. | The archive takes PDF/A-3, or the document is an e-invoice. true means this level. |
The b stands for the basic level: the file looks the same wherever it is opened. The levels u (all text can be copied as Unicode) and a (tagged and accessible) are not offered. For a file a screen reader can read, set pdf.ua beside it. PDF/A-1 is not offered either: it forbids transparency, which the browser writes for shadows, opacity, rgba colours and images with an alpha channel.
What the check proves
conformant: true means: the delivered file passed every rule of its PDF/A level that veraPDF checks. That covers the file itself: every font is embedded, colours carry an output profile, the metadata names the level, nothing is encrypted, no content depends on something outside the file, and attachments are of a kind the level allows.
It does not mean:
- that the content is right. A report with a wrong figure passes;
- that the document can be read aloud. That is PDF/UA, a standard of its own;
- that a law's demands on keeping documents are met. See What the format does not settle.
Formfeed is a tool that writes the format and checks it. It gives no legal advice: which documents you have to keep, in which format and for how long is for you to find out.
Switching it on
pdf.pdfa is a setting like any other: it is stored with the template version and merged with the request's settings, the request winning.
| Value | Meaning |
|---|---|
false or null | Off, the default. |
"2b" | Every PDF of the render is a PDF/A-2b. |
"3b" or true | Every PDF of the render is a PDF/A-3b. |
Any other value in a request, such as "1b", "3u" or an object, answers 400 invalid_request; stored in a template, such a value declares nothing, and formfeed validate reports it. It applies to PDF output only.
In the template
Open the Page setup in the editor, go to the Document section and choose a level under PDF/A for archiving: Off, PDF/A-2b or PDF/A-3b. The line under the choice says what the level means. Save and publish the version as for any other setting. In a template that makes an e-invoice, PDF/A-2b cannot be chosen, because the invoice's XML is an attachment that PDF/A-2 does not allow.
The editor's Flow and Paged previews are HTML and no PDF/A. A True render converts and checks the file and shows the verdict below its result.
In a project, the declaration is in settings.json:
{
"pdf": {
"metadata": { "title": "Annual report {{ report.year }}" },
"pdfa": "2b"
}
}
In the request
curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "annual-report",
"data": { "report": { "year": 2026 } },
"settings": { "pdf": { "pdfa": "2b" } }
}'
"pdfa": false in a request switches off what the template declares, for that request.
What fails and why
A file that does not pass fails the render with 500 pdfa_conversion_failed. Nothing is delivered and nothing is charged; the problem and the failed render carry the report with the rules that failed. Retrying does not help: the same document fails the same way.
Documents Formfeed renders from HTML pass as they are: on 31 October 2026 every PDF example on formfeed.dev became a valid PDF/A-2b and PDF/A-3b, but for the password-protected one and the e-invoice, which is a PDF/A-3b already, with the text watermark and the mark of test renders, whose fonts are embedded. What fails comes from elsewhere:
| Rule | What it means | Where it comes from | What to do |
|---|---|---|---|
6.2.11.4.1-1 | A font is used that is not embedded in the file. | A PDF appended with post.merge_after that names a font without carrying it, as many generators do with Helvetica or Times. | Create that PDF again with its fonts embedded, or append it to a render without pdf.pdfa. |
6.2.11.5-1 | An embedded font program disagrees with the widths the PDF states. | The office converter writes such fonts for OpenType fonts with CFF outlines, which on our side is the font for Chinese, Japanese and Korean text in a Word or PowerPoint template. | There is no repair: the font program would have to be written anew. Render such text from an HTML template, or leave pdf.pdfa off for it. |
6.8-5 | A file attached to the PDF is not itself PDF/A-1 or PDF/A-2. | Only at "2b": a file attached to the document that is not PDF/A itself, such as an e-invoice's XML. | Choose "3b", which takes any attached file. Formfeed never drops an attachment to make a file pass. |
Each rule is a clause of ISO 19005 with veraPDF's test number. messages names the object of the PDF in location, and the error page of pdfa_conversion_failed lists the rest. A file the converter could not read at all carries reason instead of a report. Send the render_id to support@formfeed.dev for anything that is not on this list: a document Formfeed rendered and cannot convert is ours to fix.
The report
A render that set pdf.pdfa carries pdfa:
"pdfa": {
"standard": "PDF/A-2b",
"conformant": true,
"validator": "veraPDF 1.30.2",
"id": "8f503142-6d7c-4e8f-a091-2c3d4e5f6071",
"versions": { "verapdf": "1.30.2", "pdfbox": "3.0.8" },
"pdf_sha256": "9f2c0e5d…",
"messages": []
}
| Field | Meaning |
|---|---|
standard | PDF/A-2b or PDF/A-3b. |
conformant | The file passed every rule of its level. A render delivers a file only when it did. |
validator | The validator and its version. Its rule set moves between releases, so the report says which one judged. |
id | The validator's own id for this report. |
versions | The versions of what converted and judged the file. |
pdf_sha256 | The SHA-256 of the file that was judged: the delivered file when the render succeeded, null when no file came back. |
messages | The rules the file failed, at most 50, each with severity, rule (clause and test number), message and location (the object of the PDF). Empty for a file that passed. |
truncated | Present and true when more rules failed than messages holds. |
pdfa is null on a render that did not set pdf.pdfa. A render whose file failed keeps it, and its problem carries the same block.
Where you see it:
- In the API: the answer to the render,
GET /v1/renders/{id}, the render list and therender.completedandrender.failedwebhook events. - With
?output=binarythe answer is the file, and the headerX-Formfeed-PDF-Anames the standard it passed, such asPDF/A-2b. - In the app, a render's sheet in the render log has a PDF/A section with the verdict, and for a file that failed the rules. Report (JSON) downloads the report. A True render in the editor shows the same section below its result.
- On the command line,
formfeed renderprints it.
Keep the report with the file. It records which level the file was held to, by which validator, and that the file with this hash passed, on the day it was made.
With an e-invoice
An e-invoice is a PDF/A-3b already: the invoice's XML is attached to the file, and the file is held to PDF/A-3b before it is delivered. So:
"3b"ortruebeside an e-invoice changes nothing in the file and converts nothing a second time. The render carries apdfablock made of the e-invoice's own validation, so one field answers "is this a PDF/A" for every render."2b"beside an e-invoice answers400 invalid_request, wherever each was declared: the XML is an attachment, which PDF/A-2 does not allow.- An e-invoice whose conversion fails answers with its own
pdfa_conversion_failed, carryingeinvoiceand, wherepdf.pdfawas set,pdfa.
With PDF/UA-1
pdf.pdfa and pdf.ua go together, as they do for an e-invoice:
{
"pdf": {
"metadata": { "title": "Annual report {{ report.year }}" },
"pdfa": "2b",
"ua": true
}
}
- One file, checked as both. The page is tagged and repaired as for any PDF/UA-1 file, the conversion keeps the tags, and the converted file, which is the one you receive, is held to its PDF/A level and to PDF/UA-1 in the same step.
- Two reports. The render carries
pdfaandaccessibility. When the file passed both, its metadata names both standards. - Strict. A file that is a valid PDF/A and fails PDF/UA-1 answers 422
pdfua_validation_failedand delivers nothing; the problem carriesaccessibilitywith the rules that failed andpdfa, which says that the PDF/A was in order. - Report. With
{ "check": "report" }the valid PDF/A is delivered without the PDF/UA identifier.
What it cannot be combined with
- A password.
post.passwordwithpdf.pdfaanswers400 invalid_requestwithfield: "post.password", wherever the PDF/A was declared: PDF/A forbids encryption. For an encrypted ordinary PDF from a template that declares PDF/A, send"pdfa": false. - An e-invoice at
"2b". See above. - Images. A template that declares
pdf.pdfarenders its images as ever. Asking for it in a request for an image answers400 invalid_request.
A merge (post.merge_after), a watermark, the mark of test renders, pdf.ua, Word and PowerPoint templates, batches and async renders all go with it. The conversion is the last step, after the merge, the watermark and the test mark, so what is converted and checked is the document as it will be delivered. A deduplicated render answers with the file it reuses and that file's report.
The PDF tools
/pdf/watermark and /pdf/protect refuse a PDF/A source with 422 source_invalid: a watermark drawn afterwards would change a file that was checked as it is, and a password would leave an encrypted file that still says it is PDF/A. Set the watermark on the render instead (post.watermark), where it comes before the conversion. /pdf/merge takes a PDF/A like any PDF and returns a new document, which is not PDF/A. The same goes for e-invoices, which are PDF/A-3b files.
What the format does not settle
PDF/A makes a file that can be opened as it is in decades. Keeping a document as a law asks, such as the German GoBD for business records, is more than the file: where it is stored, that it cannot be changed or deleted unnoticed, that it can be found again for the whole period, and how the procedure is documented. None of that is in the file, and Formfeed does none of it:
- Formfeed is not an archive. Outputs are kept only for the retention period of your plan and workspace. Store every file you have to keep in your own archive; Bring your own storage copies each render into your bucket as it is made.
- Archive the delivered file itself. A file another program saved again is no longer the file that was checked.
- Keep the report beside it. Its
pdf_sha256ties the verdict to the file.
Plans, units and cost
pdf.pdfa is part of every plan and costs no extra unit. Test renders, the mark of the Free plan included, and the editor's true renders are converted and checked like every other render. A failed render costs nothing. An e-invoice costs its own unit, as always.
Errors
| Status | Code | When |
|---|---|---|
| 500 | pdfa_conversion_failed | The file did not pass its PDF/A level, or could not be converted. pdfa names the rules, or reason the converter's message. |
| 422 | pdfua_validation_failed | With pdf.ua and the strict check, the valid PDF/A failed PDF/UA-1. |
| 503 | conformance_unavailable | The converter is busy or not reachable. Retry after Retry-After; a file is never delivered unchecked. |
| 504 | render_timeout | The conversion did not finish in time. Retry. |
| 400 | invalid_request | A value that is not one of those above, a password, "2b" beside an e-invoice, or an image output. |
On the command line
formfeed validate annual-report
reports a declaration in settings.json that the API refuses: a value it does not take, or "2b" beside an e-invoice. Both are errors. A declaration on an image template is a warning.
formfeed render annual-report
prints the verdict of the render (here with a test key, which costs no unit):
PDF/A-2b conformant (veraPDF 1.30.2)
rnd_7da09ab6cd9946b798c187c2a9350852: 4 page(s), 0 unit(s) -> annual-report.pdf
formfeed dev previews the template as HTML and says that only a render makes and checks the PDF/A file; its Render button does.