Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ Use this group for password protection, permissions, signatures, and standards.
- Archival/print conformance:
[convert_to_pdfa][pdfrest.PdfRestClient.convert_to_pdfa],
[convert_to_pdfx][pdfrest.PdfRestClient.convert_to_pdfx]
- Electronic invoices:
[create_zugferd_pdf][pdfrest.PdfRestClient.create_zugferd_pdf],
[validate_zugferd_pdf][pdfrest.PdfRestClient.validate_zugferd_pdf]

## PDF cleanup and rendering normalization

Expand All @@ -130,6 +133,7 @@ Use this group when the user starts with non-PDF content or needs downstream
formats.

- Into PDF:
[create_zugferd_pdf][pdfrest.PdfRestClient.create_zugferd_pdf],
[convert_office_to_pdf][pdfrest.PdfRestClient.convert_office_to_pdf],
[convert_postscript_to_pdf][pdfrest.PdfRestClient.convert_postscript_to_pdf],
[convert_email_to_pdf][pdfrest.PdfRestClient.convert_email_to_pdf],
Expand Down
4 changes: 4 additions & 0 deletions docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ to the API guide and API reference for exact method names and options.
- Apply an uploaded `.joboptions` Distiller profile when converting PostScript
or EPS with `convert_postscript_to_pdf`
- Convert to standards formats (`PDF/A`, `PDF/X`)
- Create validated ZUGFeRD or Factur-X PDF/A-3 invoices from uploaded CII XML
with optional source-PDF, logo, and render controls
- Validate existing ZUGFeRD or Factur-X PDFs for package, XML, visible-content,
and independent PDF/A-3 conformance

### Extraction and Analysis

Expand Down
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ supported interpreter matrix.
synchronously convert selected PDF pages and verify the PostScript output.
- `examples/convert_pdf_to_postscript/async_convert_pdf_to_postscript_example.py`
– asynchronously convert a PDF page and verify the PostScript output.
- `examples/create_zugferd_pdf/create_zugferd_pdf_example.py` – create a
formatted ZUGFeRD PDF/A-3 invoice from uploaded CII XML.
- `examples/validate_zugferd_pdf/validate_zugferd_pdf_example.py` – create and
validate a ZUGFeRD PDF/A-3 invoice, including content-match findings.
- `examples/delete/delete_example.py` – demonstrate file deletion (sync + async
variants).
- `examples/extract_text/extract_pdf_text_example.py` – run `extract_pdf_text`
Expand Down
72 changes: 72 additions & 0 deletions examples/create_zugferd_pdf/create_zugferd_pdf_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Create a branded ZUGFeRD PDF/A-3 invoice from CII XML.

This sample demonstrates how to:

1. Upload the checked-in invoice XML resource.
2. Generate a ZUGFeRD PDF with typed appearance and locale options.
3. Download and verify the generated PDF.
4. Print the result metadata and input resource IDs.

Set ``PDFREST_API_KEY`` and optionally ``PDFREST_BASE_URL`` for a self-hosted
deployment, then run from the repository root with
``uv run examples/create_zugferd_pdf/create_zugferd_pdf_example.py``. The
required XML input is included in the repository.
"""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient
from pdfrest.types import PdfZugferdRenderOptions

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
INVOICE_PATH = RESOURCE_DIRECTORY / "zugferd-minimum-invoice.xml"


def create_zugferd_pdf() -> None:
"""Upload invoice resources, create the PDF/A-3 invoice, and verify it."""
load_dotenv()
base_url = os.getenv("PDFREST_BASE_URL")
render_options = PdfZugferdRenderOptions(
font="courier",
bold_font="courierbold",
accent_color=(20, 64, 115),
locale="de-DE",
label_language="en",
currency_display="symbol",
date_format="dd.MM.yyyy",
decimal_precision=2,
footer_message="Thank you for your business.",
)

with PdfRestClient(base_url=base_url) as client:
invoice = client.files.create_from_paths(INVOICE_PATH)[0]
response = client.create_zugferd_pdf(
invoice,
render_options=render_options,
output="zugferd-invoice",
)
pdf_bytes = client.files.read_bytes(response.output_file)

if not pdf_bytes.startswith(b"%PDF-"):
msg = "The generated resource is not a PDF."
raise RuntimeError(msg)

output = response.output_file
print(f"Created: {output.name}")
print(f"MIME type: {output.type}")
print(f"Size: {output.size} bytes")
print(f"Input IDs: {', '.join(response.input_ids)}")
print(f"Download URL: {output.url}")


if __name__ == "__main__": # pragma: no cover - manual example
create_zugferd_pdf()
51 changes: 51 additions & 0 deletions examples/resources/zugferd-minimum-invoice.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Reduced from the official ZUGFeRD 2.4 MINIMUM_Rechnung.xml example.
This deterministic fixture exercises the smallest supported invoice profile.
-->
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:factur-x.eu:1p0:minimum</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>471102</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20200305</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>Lieferant GmbH</ram:Name>
<ram:PostalTradeAddress>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="FC">201/113/40209</ram:ID>
</ram:SpecifiedTaxRegistration>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE123456789</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Kunden AG Frankreich</ram:Name>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery/>
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:TaxBasisTotalAmount>198.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="EUR">37.62</ram:TaxTotalAmount>
<ram:GrandTotalAmount>235.62</ram:GrandTotalAmount>
<ram:DuePayableAmount>235.62</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
59 changes: 59 additions & 0 deletions examples/validate_zugferd_pdf/validate_zugferd_pdf_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Create and validate a ZUGFeRD PDF/A-3 invoice.

This sample demonstrates how to:

1. Upload the checked-in CII invoice XML resource.
2. Create a ZUGFeRD PDF/A-3 invoice to use as deterministic validation input.
3. Validate its embedded XML, package metadata, visible content, and PDF/A-3
conformance.
4. Print the typed validation status and any findings.

Set ``PDFREST_API_KEY`` and optionally ``PDFREST_BASE_URL`` for a self-hosted
deployment, then run from the repository root with
``uv run examples/validate_zugferd_pdf/validate_zugferd_pdf_example.py``. The
required XML input is included in the repository.
"""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
INVOICE_PATH = RESOURCE_DIRECTORY / "zugferd-minimum-invoice.xml"


def validate_zugferd_pdf() -> None:
"""Create a deterministic ZUGFeRD PDF and validate its conformance."""
load_dotenv()
base_url = os.getenv("PDFREST_BASE_URL")

with PdfRestClient(base_url=base_url) as client:
invoice = client.files.create_from_paths(INVOICE_PATH)[0]
created = client.create_zugferd_pdf(
invoice,
output="zugferd-validation-example",
)
validation = client.validate_zugferd_pdf(created.output_file)

print(f"Status: {validation.status}")
print(f"PDF/A-3 conformant: {validation.pdfa}")
print(f"Content match: {validation.content_match_status}")
print(f"Input ID: {validation.input_id}")
print(f"Input URL: {validation.input_url}")
if validation.message:
print(f"Notice: {validation.message}")
for finding in validation.findings or []:
print(f"{finding.severity}: {finding.code} — {finding.message}")


if __name__ == "__main__": # pragma: no cover - manual example
validate_zugferd_pdf()
Loading
Loading