Die Adresse, die Ihr Programm aufruft, und ihre Antworten
Ihr Programm sendet die Beschreibung eines Dokuments an eine Adresse, unter dem API-Schlüssel seines Kontos, mit den Dateien, die diese Beschreibung nennt, und das fertige PDF kommt in der Antwort zurück.
Der Aufruf
Eine Adresse antwortet, und sie antwortet auf ein POST. Der Body dieses POST ist die Beschreibung des gewünschten Dokuments. Der Schlüssel Ihres Kontos reist im Header Authorization, als Bearer-Token.
Was in einer Beschreibung stehen darf — die Seite, die darauf gezeichneten Elemente, die Schriften, die Norm, nach der sie geschrieben ist —, ist Gegenstand des restlichen API-Bereichs, den diese Seite nicht wiederholt.
| Der Aufruf, der ein PDF erzeugt | POST https://hqf-pdf.com/api/v1/render |
|---|---|
| Die Form, in der eine Beschreibung allein gesendet wird | application/json |
| Die Form, in der eine Beschreibung mit Dateien gesendet wird | multipart/form-data |
| Der Teil, in dem die Beschreibung reist | json_data |
| Jeder andere Teil | eine Datei, unter dem Namen, den die Beschreibung ihr gibt |
| Der größte Body, den diese Adresse liest, samt Dateien | 536 870 912 (512 MiB) |
| Die Schrift, die der Dienst vorhält, die eine Beschreibung nennt und für die nichts gesendet wird | DejaVuSans |
| Wie der API-Schlüssel übertragen wird | Authorization: Bearer <key> |
| Was ein Erfolg trägt | 200 application/pdf |
| Der Name, unter dem die Datei angeboten wird | Content-Disposition: attachment; filename="document.pdf" |
| Wie viele Seiten das fertige Dokument hat | x-hqf-pages: 4 |
| Zu wie vielen Dingen die Engine einen Hinweis gemacht hat | x-hqf-warnings: 2 |
| Die Hinweise selbst, durch Kommas getrennt unter einem Header-Namen | x-hqf-warning: <value>,<value> |
| Wie viele Befunde das Dokument von den verlangten Normen trennen | x-hqf-findings: 1 |
| Die Befunde selbst, durch Kommas getrennt unter einem Header-Namen | x-hqf-finding: <value>,<value> |
| Was eine Ablehnung trägt | application/json {"error": "…", "kind": "…"} |
| Was ein Monat ohne verbleibendes Kontingent daneben trägt | Retry-After: Sat, 05 Sep 2026 00:00:00 GMT |
Was neben der Beschreibung reist
Eine Beschreibung, die eine Vorlage stempelt, ein Bild zeichnet, eine elektronische Rechnung anfordert oder eine Farbe über ein Profil angibt, nennt den Namen einer Datei, nie ihre Bytes. Diese Bytes reisen neben der Beschreibung, im selben Aufruf.
Ein solcher Aufruf wird als multipart/form-data gesendet: die Beschreibung im Teil namens json_data und ein Teil pro Datei. Ihre Dateien benennen Sie, wie Sie möchten; was einen Teil mit seinem Zweck verbindet, ist die Beschreibung, die sagt, welcher Name welche Rolle spielt. Jeder Teil außer dem der Beschreibung muss einen eigenen Dateinamen tragen: Ein als einfacher Text gesendeter Teil zählt als Text, nicht als Datei.
Eine Beschreibung, die eine Datei nennt, die der Body nicht enthält, wird abgelehnt, bevor etwas weitergeleitet ist, und die Ablehnung nennt die Datei. Eine Beschreibung, die gar keine Datei nennt, wird für sich allein als JSON gesendet.
Die 536 870 912 Bytes, die diese Adresse liest, decken den ganzen Body ab: die Beschreibung und jede Datei, die sie nennt, zusammen gewogen.
Derselbe Aufruf, dreimal
Die drei werden für ein Konto beantwortet, das nichts zahlt. Ihr Text ist in DejaVuSans gesetzt, der Schrift, die der Dienst vorhält, und deshalb kommen sie durch: Eine Beschreibung, die eine der vierzehn Schriften nennt, die jede Anzeigesoftware bereits mitbringt, wird mit 402 unter needs_a_licence beantwortet, bis das Konto einen Tarif bezahlt. Der Abschnitt darunter sagt, was ein solches Konto sonst erhält.
Aus einer Shell
Der Body ist die Beschreibung selbst. Die Antwort ist das PDF, schreiben Sie sie also direkt in eine Datei.
curl -X POST https://hqf-pdf.com/api/v1/render \
-H "Authorization: Bearer $HQF_PDF_KEY" \
-H "Content-Type: application/json" \
--data-binary @invoice.json \
-o invoice.pdf
Aus Python
Eine Ablehnung kommt als JSON mit einem Satz, und der Status sagt, ob es sich lohnt, die Anfrage erneut zu senden.
import os
import pathlib
import requests
KEY = os.environ["HQF_PDF_KEY"]
description = {
"items": [
{
"type": "text",
"rect": {"llx": 56, "lly": 700, "urx": "{page_width} - 56", "ury": 780},
"content": ["Invoice 2026-014"],
"font": "DejaVuSans",
"font_size": 24,
}
],
}
answer = requests.post(
"https://hqf-pdf.com/api/v1/render",
headers={"Authorization": f"Bearer {KEY}"},
json=description,
timeout=120,
)
if answer.status_code != 200:
raise SystemExit(answer.json()["error"])
pathlib.Path("invoice.pdf").write_bytes(answer.content)
Mit einer Datei neben der Beschreibung
Die Beschreibung nennt ihre Dateien; jede reist in einem Teil desselben Bodys, unter genau dem Namen, den die Beschreibung ihr gibt. Hier nennt eine elektronische Rechnung ihr XML `invoice_xml`, also heißt der Teil, der dieses XML trägt, ebenfalls `invoice_xml`.
import json
import os
import pathlib
import requests
KEY = os.environ["HQF_PDF_KEY"]
description = {
"invoice": {
"xml": "invoice_xml",
"profile": "EN 16931",
"relationship": "alternative",
"issued": "2026-08-23T09:30:00+02:00",
},
"archive": "pdfa3b",
"items": [
{
"type": "text",
"rect": {"llx": 56, "lly": 700, "urx": "{page_width} - 56", "ury": 780},
"content": ["Invoice 2026-014"],
"font": "DejaVuSans",
"font_size": 24,
}
],
}
answer = requests.post(
"https://hqf-pdf.com/api/v1/render",
headers={"Authorization": f"Bearer {KEY}"},
files={
"json_data": ("description.json", json.dumps(description), "application/json"),
"invoice_xml": ("factur-x.xml", pathlib.Path("factur-x.xml").read_bytes()),
},
timeout=120,
)
if answer.status_code != 200:
raise SystemExit(answer.json()["error"])
pathlib.Path("invoice.pdf").write_bytes(answer.content)
Was ein Konto erhält, das nichts zahlt
Jede Seite kommt mit einem Wasserzeichen heraus: quer darübergeschriebenen Worten. Unter dem Wasserzeichen sind die Seiten genau so gesetzt, wie ein bezahlter Tarif sie setzt, mit einem Unterschied: Jeder Buchstabe wird als Form nachgezeichnet, statt als Text geschrieben zu werden. Kein Text auf der Seite lässt sich markieren, durchsuchen oder kopieren; die Datei trägt keine Schrift und ist schwerer als dieselbe als Text geschriebene Seite. Das Nachzeichnen eines Buchstabens braucht seine Glyphe; eine Seite kommt also nur heraus, wenn jede Schrift, in der ihr Text gesetzt ist, entweder mit Ihrer Anfrage gesendet wird oder vom Dienst bereits vorgehalten wird. Der Editor auf dieser Website und die Beispiele auf der API-Seite sind so eingerichtet. Alles andere erfordert eine kostenpflichtige Lizenz: Der Dienst erzeugt kein Dokument, und seine Antwort nennt, was verlangt wurde, und sagt, dass ein Lizenzschlüssel es abdeckt.
Das sind die Anfragen, die sie mit 402 beantwortet, statt zu schreiben:
- Text, gesetzt in einer der vierzehn Schriften, die die Anzeigesoftware bereits hat, in einer Schrift, deren Buchstaben Glyphen sind, die die Anfrage selbst beschreibt, oder in einem Type-1-Programm: Keine der drei liefert eine Glyphe zum Nachzeichnen.
- Eine Seite aus einem eigenen Dokument, die mehr als zehntausend Wörter zeigt, oder eine, deren Wörter sich nicht zählen lassen: Eine solche Seite wird unverändert übernommen, und ihr Text ist Text.
- Eine Angabe, dass das Dokument die Barrierefreiheitsregeln oder die Archivierungsstufe A erfüllt; sie beruht darauf, dass jede Markierung auf der Seite sagt, wofür sie steht: Eine Seite aus nachgezeichneten Formen sagt nichts über ihre Markierungen.
- Ein Link oder ein Lesezeichen, das zu einem Teil der eigenen Struktur des Dokuments führt.
Jede Antwort und was dann zu tun ist
Eine Ablehnung ist JSON mit einem Satz und einem Wort. Der Satz sagt, was falsch ist, in der Sprache dessen, der ihn liest; das Wort unter kind liest ein Programm. Es stammt aus der geschlossenen Liste unten und wird nie übersetzt.
Der Status allein sagt nicht, was zu tun ist: Zwei Ablehnungen unter einem Status können Entgegengesetztes verlangen, und kind unterscheidet sie. Ein Wort, das diese Seite nicht auflistet, stammt von einem Server, der neuer ist als diese Seite, und ist so zu behandeln, wie der Status es sagt.
Die einzige Antwort, bei der es sich lohnt, dieselbe Anfrage sofort erneut zu senden, ist die, die sagt, dass der Server nie erreicht wurde. Ein Monat ohne verbleibendes Kontingent lohnt ebenfalls ein erneutes Senden, sobald der Zeitpunkt gekommen ist, den Retry-After nennt.
| Antwort | Das Wort unter kind | Was es bedeutet | Was zu tun ist |
|---|---|---|---|
200 |
Das PDF ist der Body, so übergeben, wie es erzeugt wird, unter dem Dateinamen, den der obige Header nennt. Daneben stehen die Seitenzahl, wozu die Engine Hinweise gemacht hat und was den Normen entgegensteht, die check verlangt hat. |
Nichts zu tun. | |
400 |
not_json · no_description · no_such_part · bad_part_name · description · drawing · form
|
Entweder ist das Gesendete gar kein JSON, oder der Body enthält keine Beschreibung, oder die Beschreibung nennt eine Datei, die der Body nicht enthält, oder ein Teil des Bodys trägt einen Namen, den diese Adresse nicht annimmt, oder der Server hat die Beschreibung gelesen und abgelehnt. Das Wort sagt, welches davon, und der Satz sagt, was gefunden wurde. | Korrigieren Sie die Beschreibung. Dieselbe zweimal gesendet ergibt dieselbe Antwort. |
400 |
client_name · call_number · render_key
|
Der Server nahm den Namen, die Nummer oder den Schlüssel nicht an, die diese Website auf den Aufruf schreibt. Keines der drei gehört Ihnen: Der Name ist der, den Ihr API-Schlüssel trägt, die Nummer wird hier erzeugt, eine pro Aufruf, und der Schlüssel ist der, den diese Website dem Server vorlegt, an den sie weiterleitet. | Nichts auf Ihrer Seite, und dieselbe Anfrage erneut zu senden ändert nichts. Schreiben Sie an contact@hqf-pdf.com und nennen Sie den Zeitpunkt des Aufrufs. |
401 |
no_key
|
Der Aufruf trug keinen API-Schlüssel oder einen, den niemand mehr besitzt. | Legen Sie einen aktiven API-Schlüssel des Kontos vor. |
402 |
needs_a_licence
|
Die Anfrage verlangte etwas, das ein Lizenzschlüssel abdeckt, und das Konto hat keinen. Der Satz nennt, was verlangt wurde. | Ändern Sie die Anfrage oder erwerben Sie eine Lizenz. Dieselbe noch einmal zu senden ändert nichts, wann auch immer sie gesendet wird. |
405 |
wrong_method
|
Die Adresse wurde mit einer anderen Methode als POST aufgerufen. | Senden Sie die Beschreibung. Dieselbe Adresse, in einem Browser geöffnet, zeigt eine Seite. |
411 |
no_body_length
|
Der Body gibt keine Länge an. Ein in Stücken oder ohne diesen Header gesendeter Body erreicht diese Adresse leer und wird daher abgelehnt, statt als leere Beschreibung gelesen zu werden. | Geben Sie Content-Length an und senden Sie den Body in einem Stück. |
413 |
body_too_large · too_many_pages
|
Der Body der Anfrage überschreitet die 536 870 912 Bytes, die diese Adresse liest, oder das verlangte Dokument überschreitet eine Obergrenze des Servers. Das Wort sagt, welches, und der Satz nennt die Größe. | Verlangen Sie weniger in einem Aufruf. |
429 |
no_documents_left
|
Die im Monat enthaltenen Dokumente sind alle verbraucht. Retry-After trägt den Zeitpunkt des Monatswechsels als Datum: Der Monat ist der, nach dem das Konto abgerechnet wird, vom Tag des Abonnements bis zum selben Tag des Folgemonats, nicht der Kalendermonat. |
Warten Sie bis zu dem Zeitpunkt, den Retry-After nennt, und senden Sie dieselbe Anfrage erneut, oder wechseln Sie zu einem höheren Tarif, was ab dem bereits laufenden Monat gilt. |
500 |
server
|
Das Schreiben des PDF ist auf der Seite des Servers fehlgeschlagen. | Einen weiteren Aufruf wert. Sagen Sie uns, wenn es weiterhin geschieht. |
503 |
unreachable
|
Der Server ließ sich gar nicht erreichen, oder diese Website weiß nicht, wo er ist. Von der Beschreibung wurde nichts gelesen. | Die einzige Antwort, bei der es sich lohnt, dieselbe Anfrage erneut zu senden. |
Unter jeder der obigen Nummern kann ein weiteres Wort ankommen: `other`. Es bedeutet, dass der Server abgelehnt und nichts über die Art gesagt hat; so verhält sich ein Server, der älter ist als das Wort selbst. Lesen Sie die Nummer und sagen Sie es uns.
Was neben der Datei zurückkommt
Ein Erfolg trägt drei Zählungen. x-hqf-pages gibt an, wie viele Seiten das fertige Dokument hat, und wird neben der Erzeugung festgehalten; der Aufruf zählt als ein Dokument, was es auch angibt. x-hqf-warnings gibt an, zu wie vielen Dingen die Engine beim Schreiben einen Hinweis gemacht hat. x-hqf-findings gibt an, wie viele Befunde zwischen dem fertigen Dokument und den Normen stehen, die der Schlüssel check Ihrer Beschreibung genannt hat. Alle drei stehen bei jedem Erfolg, auch bei 0; eine Antwort ohne sie stammt von einem Server, der älter ist als sie.
Die Hinweise selbst kommen unter x-hqf-warning und die Befunde unter x-hqf-finding. Jeder Name wird einmal angegeben, seine Werte durch ein Komma getrennt: zuerst am Komma schneiden, dann jedes Stück dekodieren — in dieser Reihenfolge, nie umgekehrt. Ein Komma innerhalb eines Werts steht als %2C, sodass der Schnitt nie in einen Wert fällt.
Jeder Wert wird so geschrieben, wie ein Header ihn trägt: Jedes Byte außer einem druckbaren ASCII-Byte, und das Prozentzeichen selbst, steht als %XX. Höchstens zehn Werte unter jedem der beiden Namen, zusammen bis zu tausend Bytes, je nachdem, was zuerst erreicht ist; der Wert, der über das Verbleibende hinausläuft, wird gekürzt und endet auf %E2%80%A6. Die Zählungen nennen in jedem Fall die Gesamtzahl, sodass ein Kunde, der x-hqf-warnings: 50 neben zehn Werten liest, trotzdem die wahre Zahl hat.
Keine Antwort sagt, wie lange die Arbeit gedauert hat, weder hier noch auf einem eigenen Server: Die beiden Dauern, die der Dienst misst, gehen in sein eigenes Protokoll, und kein Header trägt sie zurück.
Was ein Aufruf kostet
- Ein Dokument wird vom Monat abgezogen, bevor die Beschreibung weitergeleitet wird.
- Eine Erzeugung, die nie ankommt, gibt ihr Dokument zurück: Ein mit 503 beantworteter Aufruf kostet nichts.
- Ein Aufruf, der abgelehnt wird, bevor etwas weitergeleitet ist — kein API-Schlüssel, ein Body, der kein JSON ist, eine Beschreibung, die eine Datei nennt, die der Body nicht enthält, ein Monat ohne verbleibendes Kontingent —, kostet ebenfalls nichts.
- Die aufgezeichneten Seiten sind die, die der Server angibt; diese Website öffnet das Dokument nicht, um sie erneut zu zählen.
Von Hand ausprobieren
Dieselbe Adresse, in einem Browser geöffnet, zeigt ein Feld: Fügen Sie eine Beschreibung ein, und das Dokument kommt sofort zurück. Es erzeugt das Dokument für den, der angemeldet ist, aus der Browsersitzung statt mit einem API-Schlüssel, und was es erzeugt, wird nicht vom Monat abgezogen.
Es ist dieselbe Adresse, dieselbe Beschreibung und derselbe Server, sodass das, was Sie von Hand ausprobieren, das ist, was Ihr Programm senden wird. Das Feld trägt allerdings nur die Beschreibung: Auf der Seite gibt es keinen Platz für eine Datei, eine Beschreibung, die eine nennt, wird also aus Ihrem eigenen Programm ausprobiert.
Wohin als Nächstes
Das Format einer Beschreibung Ihr Konto: seine Lizenzschlüssel und seine Rechnungen Was mit einem gesendeten Dokument geschieht