Skip to main content
Gotenberg 9A Docker-based API for PDF

The PDF press,
rebuilt.

docker run --rm -p "3000:3000" gotenberg/gotenberg:9

Converts web pages and office documents to PDF, then remediates, signs and delivers them in the same request. Validates any PDF against PDF/A, PDF/UA and Factur-X.

  • Pure-Go PDF engine
  • 26 PDF stages
  • PDF/A · PDF/UA · Factur-X
Source
paperSize
landscape
297 × 420mm

Without paperSize, Chromium prints on Letter. Or set paperWidth and paperHeight yourself, in pt, px, in, mm, cm or pc.

Canvas · every paperSize, to scaleA3 portrait
A3
297 × 420 mm
297 × 420 mm
Chromium · /forms/chromium/convert/url
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://example.com \
--form paperSize=A3 \
-o example.pdf

Sponsors

TheCodingMachinepdfmePDFBoltFileToPDF

Powered by

01Conformance

Print anything. Prove it.

Validates any PDF against PDF/A, PDF/UA and Factur-X, clause by clause, and remediates what fails.

litho, a pure-Go PDF processor, takes over from QPDF, pdfcpu, pdftk and ExifTool, the tools behind Gotenberg 8's PDF engines. PDF/A and PDF/UA rules carry veraPDF's clause numbers.

POST/forms/pdf/validate · files=@report.pdf · profile=PDF/A-4Example run
6.7.2.1 · Metadata6.2.4.3 · ×96.1.2 · Header
11fatal violationsPDF/A-4 · ISO 19005-4
  1. 6.1.2The file header shall begin at byte zero and shall consist of "%PDF-2.n" followed by a single EOL marker.test 1 · header "%PDF-1.7" does not match required pattern×1
  2. 6.2.4.3DeviceRGB shall only be used if a device independent DefaultRGB colour space has been set.test 2 · page 1: DeviceRGB used without DefaultRGB or RGB OutputIntent×9
  3. 6.7.2.1The document catalog dictionary of a conforming file shall contain the Metadata key.test 1 · catalog missing or invalid /Metadata stream×1

The JSON answer says "compliant": false, with the specification, clause, test number, context and severity of each violation.

profile · pick one of 14

PDF/A-4, validated

The validate route takes the profile, and a password for encrypted files. It answers JSON, one report per file.

litho · /forms/pdf/validate
curl \
--request POST http://localhost:3000/forms/pdf/validate \
--form files=@report.pdf \
--form profile=PDF/A-4
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"report.pdf": {
"profile": "PDF/A-4",
"compliant": false,
"violations": [
{ "clause": "6.1.2", "testNumber": 1, "severity": "fatal" },
{ "clause": "6.2.4.3", "testNumber": 2, "severity": "fatal" },
{ "clause": "6.7.2.1", "testNumber": 1, "severity": "fatal" }
]
}
}
# The real run: 11 violations, 1 + 9 + 1, all in ISO_19005_4. Specification, description and context left out here.
02E-invoicing

Factur-X, ZUGFeRD. XRechnung too.

Send the invoice with its Cross-Industry Invoice XML: one remediate stage returns a PDF/A-3 hybrid invoice.

Embedded per Factur-X 1.07.2 and ZUGFeRD 2.3.2. Already hybrid? Leave out the XML: the same stage repairs the existing Factur-X. Validation adds Schematron warnings that do not fail conformance on their own.

What the level decides

LibreOffice · /forms/libreoffice/convert
curl \
--request POST http://localhost:3000/forms/libreoffice/convert \
--form files=@invoice.docx \
--form files=@invoice.xml \
--form 'stages=[
{ "op": "remediate", "profile": "Factur-X", "xml": "invoice.xml", "level": "EN 16931" }
]' \
-o invoice.pdf
Response
HTTP/1.1 200 OK
Content-Type: application/pdf
# PDF/A-3, with invoice.xml embedded as factur-x.xml, AFRelationship Alternative.
03Deliver

Asynchronous, straight to S3.

One request in. The PDF lands in your bucket, an event tells you. Your server never touches the bytes.

Gotenberg fetches the source from a presigned GET URL and answers 204. It then uploads the PDF to a presigned PUT URL and sends an event to your events URL.

Gotenberg-Webhook-Method
Input
Optional
  1. 1Your app → GotenbergPOST /forms/… · Gotenberg-Webhook-*
  2. 2S3 · in.docx → GotenbergGET · downloadFrom
  3. 3Gotenberg → Your app204 No Content · once downloaded
  4. 4Gotenberg → S3 · out.pdfPUT · Gotenberg-Webhook-Url
  5. 5Gotenberg → Events URLevent · Gotenberg-Webhook-Events-Url
  6. 6Gotenberg → Your collectorOTLP · OpenTelemetry
1–6 the order of one requestThe method and target follow your pick aboveEvents carry the request's correlation ID
LibreOffice · /forms/libreoffice/convert
curl \
--request POST http://localhost:3000/forms/libreoffice/convert \
--form 'downloadFrom=[
{ "url": "https://bucket.s3.amazonaws.com/in.docx?X-Amz-Signature=…" }
]' \
--header 'Gotenberg-Webhook-Url: https://bucket.s3.amazonaws.com/out.pdf?X-Amz-Signature=…' \
--header 'Gotenberg-Webhook-Method: PUT' \
--header 'Gotenberg-Webhook-Events-Url: https://api.example.com/events'
Response
HTTP/1.1 204 No Content
# Once downloadFrom is fetched; the work goes on in the background.
# Then, on https://api.example.com/events:
{"event": "webhook.success", "correlationId": "…", "timestamp": "…"}
04Operate

26 stages write. 5 routes read.

Write stages change the PDF and send it back. Read routes leave it as it is and answer JSON.

Gotenberg checks the order of the stages before it runs them: decrypt comes first, render last, and nothing rewrites a signature.

All 26 stages, in the documentation
litho · /forms/pdf/pipe
curl \
--request POST http://localhost:3000/forms/pdf/pipe \
--form files=@report.pdf \
--form files=@annex.pdf \
--form 'stages=[
{ "op": "merge" },
{ "op": "set-metadata", "title": "Q3 report" },
{ "op": "remediate", "profile": "PDF/A-4" },
{ "op": "optimize" }
]' \
-o archive.pdf
Response
HTTP/1.1 200 OK
Content-Type: application/pdf
Gotenberg-Remediation-Residual: …
Gotenberg-Remediation-Rolled-Back: …
05Architectures

Runs where you run.

Nine images on five architectures.

The full image carries Chromium and LibreOffice; the -chromium and -libreoffice images carry one of them. AWS Lambda and Cloud Run get their own images.

gotenberg/gotenbergamd64arm64arm/v7386ppc64le
:9
:9-chromium
:9-libreoffice
:9-aws-lambda
:9-chromium-aws-lambda
:9-libreoffice-aws-lambda
:9-cloudrun
:9-chromium-cloudrun
:9-libreoffice-cloudrun

Start with one command.

docker run --rm -p "3000:3000" gotenberg/gotenberg:9