L’adresse que votre programme appelle, et ses réponses
Votre programme envoie la description d’un document à une seule adresse, sous la clé d’API de son compte, avec les fichiers que cette description nomme, et le PDF fini revient dans la réponse.
L’appel
Une seule adresse répond, et elle répond à un POST. Le corps de ce POST est la description du document que vous voulez. La clé de votre compte voyage dans l’en-tête Authorization, sous forme de jeton porteur.
Ce qu’une description peut contenir — la page, les éléments dessinés dessus, les polices, la norme à laquelle elle est écrite — fait l’objet du reste de la section API, que cette page ne répète pas.
| L’appel qui crée un PDF | POST https://hqf-pdf.com/api/v1/render |
|---|---|
| La forme dans laquelle s’envoie une description seule | application/json |
| La forme dans laquelle s’envoie une description qui nomme des fichiers | multipart/form-data |
| La partie dans laquelle voyage la description | json_data |
| Toute autre partie | un fichier, sous le nom que la description lui donne |
| Le plus gros corps que cette adresse lit, fichiers compris | 536 870 912 (512 Mio) |
| La police que le service détient, qu’une description nomme sans rien envoyer | DejaVuSans |
| Le trajet de la clé d’API | Authorization: Bearer <key> |
| Ce que contient une réussite | 200 application/pdf |
| Le nom sous lequel le fichier est proposé | Content-Disposition: attachment; filename="document.pdf" |
| Combien de pages compte le document fini | x-hqf-pages: 4 |
| Le nombre de remarques du moteur | x-hqf-warnings: 2 |
| Les remarques elles-mêmes, séparées par des virgules sous un seul nom d’en-tête | x-hqf-warning: <value>,<value> |
| Combien de constats séparent le document des normes demandées | x-hqf-findings: 1 |
| Les constats eux-mêmes, séparés par des virgules sous un seul nom d’en-tête | x-hqf-finding: <value>,<value> |
| Ce que contient un refus | application/json {"error": "…", "kind": "…"} |
| Ce qu’un mois épuisé ajoute à côté | Retry-After: Sat, 05 Sep 2026 00:00:00 GMT |
Ce qui voyage à côté de la description
Une description qui appose un modèle de page, place une image, demande une facture électronique ou exprime une couleur au moyen d’un profil donne le nom d’un fichier, jamais ses octets. Ces octets voyagent à côté de la description, dans le même appel.
Un tel appel est envoyé en multipart/form-data : la description dans la partie nommée json_data, et une partie par fichier. Vous nommez vos fichiers comme vous voulez ; ce qui relie une partie à ce à quoi elle sert, c’est la description, qui dit quel nom tient quel rôle. Toute partie autre que celle de la description doit porter un nom de fichier à elle : une partie envoyée en texte simple compte pour du texte, pas pour un fichier.
Une description qui nomme un fichier que le corps de la requête ne contient pas est refusée avant que quoi que ce soit ne soit transmis, et le refus nomme le fichier. Une description qui ne nomme aucun fichier s’envoie en JSON, toute seule.
Les 536 870 912 octets que lit cette adresse couvrent le corps entier : la description et chacun des fichiers qu’elle nomme, pesés ensemble.
Le même appel, trois fois
Les trois obtiennent une réponse sur un compte qui ne paie rien. Leur texte est composé en DejaVuSans, la police que le service détient, et c’est ce qui les fait passer : une description qui nomme l’une des quatorze polices que tout logiciel de lecture possède déjà reçoit 402 sous needs_a_licence tant que le compte ne paie pas d’offre. La section suivante dit ce qu’un tel compte obtient d’autre.
Depuis un terminal
Le corps est la description elle-même. La réponse est le PDF : écrivez-la directement dans un fichier.
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
Depuis Python
Un refus arrive en JSON, avec une phrase, et le code de statut dit si la requête vaut la peine d’être renvoyée.
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)
Avec un fichier à côté de la description
La description nomme ses fichiers ; chacun voyage dans une partie du même corps, sous exactement le nom que la description lui donne. Ici une facture électronique nomme son XML `invoice_xml`, donc la partie qui contient ce XML se nomme `invoice_xml` elle aussi.
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)
Ce qu’obtient un compte qui ne paie rien
Chaque page sort avec un filigrane : un texte écrit en travers de la page. Sous le filigrane, les pages sont composées exactement comme avec une offre payante, à une différence près : chaque lettre est tracée comme une forme au lieu d’être écrite comme du texte. Aucun texte de la page ne peut être sélectionné, recherché ni copié ; le fichier ne contient aucune police, et il est plus lourd que la même page écrite en texte. Tracer une lettre demande son glyphe, donc une page ne sort que si chaque police dans laquelle son texte est composé est envoyée avec votre requête ou déjà détenue par le service. L’éditeur de ce site et les exemples de la page de l’API sont réglés ainsi. Tout le reste demande une licence payante : le service ne crée aucun document, et sa réponse nomme ce qui a été demandé et dit qu’une clé de licence le couvre.
Voici les requêtes auxquelles il répond 402 au lieu de les écrire :
- Un texte composé dans l’une des quatorze polices que le logiciel de lecture possède déjà, dans une police dont les lettres sont des glyphes que la requête décrit elle-même, ou dans un programme Type 1 : aucun des trois ne donne de glyphe à tracer.
- Une page reprise d’un document à vous qui montre plus de dix mille mots, ou dont les mots ne se comptent pas : une telle page entre telle quelle, et son texte reste du texte.
- Une déclaration qui affirme que le document respecte les règles d’accessibilité, ou le niveau A d’archivage, et qui repose sur le fait que chaque marque de la page dise ce qu’elle représente : une page faite de formes tracées ne dit rien de ses marques.
- Un lien ou un signet qui mène à un morceau de la structure du document.
Toutes les réponses, et ce qu’il faut faire de chacune
Un refus arrive en JSON, avec une phrase et un mot. La phrase dit ce qui ne va pas, dans la langue de qui la lit ; le mot, sous kind, est ce que lit un programme. Il vient de la liste fermée ci-dessous et n’est jamais traduit.
Le statut seul ne dit pas quoi faire : deux refus sous un même statut peuvent appeler des gestes opposés, et c’est kind qui les distingue. Un mot que cette page ne liste pas vient d’un serveur plus récent qu’elle, et se traite comme le statut l’indique.
La seule réponse qui mérite qu’on renvoie aussitôt la même requête est celle qui dit que le serveur n’a jamais été joint. Un mois dont il ne reste rien mérite aussi un nouvel envoi, une fois venu le moment que Retry-After indique.
| Réponse | Le mot sous kind | Ce que cela veut dire | Que faire |
|---|---|---|---|
200 |
Le PDF est le corps de la réponse, remis au fur et à mesure de sa création, sous le nom de fichier que l’en-tête ci-dessus indique. À côté figurent le nombre de pages, ce que le moteur a remarqué, et ce qui enfreint les normes que check a demandé de vérifier. |
Rien à faire. | |
400 |
not_json · no_description · no_such_part · bad_part_name · description · drawing · form
|
Soit ce qui a été envoyé n’est pas du JSON, soit le corps ne contient aucune description, soit la description nomme un fichier que le corps ne contient pas, soit une partie du corps est nommée d’une manière que cette adresse n’accepte pas, soit le serveur a lu la description et l’a refusée. Le mot dit lequel, et la phrase dit ce qu’il a trouvé. | Corrigez la description. La même envoyée deux fois donne la même réponse. |
400 |
client_name · call_number · render_key
|
Le serveur a refusé le nom, le numéro ou la clé que ce site inscrit sur l’appel. Aucun des trois ne vient de vous : le nom est celui de votre clé d’API, le numéro est fabriqué ici, un par appel, et la clé est celle que ce site présente au serveur auquel il transmet. | Rien de votre côté, et renvoyer la même requête n’y change rien. Écrivez à contact@hqf-pdf.com en indiquant le moment de l’appel. |
401 |
no_key
|
L’appel n’avait aucune clé d’API, ou une clé d’API que plus personne ne détient. | Présenter une clé d’API active du compte. |
402 |
needs_a_licence
|
La requête porte sur quelque chose qu’une clé de licence couvre, et le compte n’en a aucune. La phrase nomme ce qui a été demandé. | Changez la requête, ou prenez une licence. Renvoyer la même ne change rien, à quelque moment que ce soit. |
405 |
wrong_method
|
L’adresse a été appelée avec une méthode autre que POST. | Envoyez la description. La même adresse ouverte dans un navigateur montre une page. |
411 |
no_body_length
|
Le corps n’indique pas sa taille. Un corps envoyé par morceaux, ou dont l’en-tête manque, arrive vide à cette adresse : il est refusé plutôt que lu comme une description vide. | Indiquez Content-Length, et envoyez le corps d’un seul tenant. |
413 |
body_too_large · too_many_pages
|
Le corps de la requête dépasse les 536 870 912 octets que cette adresse lit, ou le document demandé dépasse un plafond du serveur. Le mot dit lequel, et la phrase donne la taille. | Demandez moins en un seul appel. |
429 |
no_documents_left
|
Les documents compris dans le mois sont tous utilisés. Retry-After donne le moment où le mois se remet à zéro, sous forme de date : le mois est celui sur lequel le compte est facturé, du jour de l’abonnement au même jour du mois suivant, et non celui du calendrier. |
Attendez le moment que Retry-After indique et renvoyez la même requête, ou passez à l’offre supérieure, qui prend effet sur le mois déjà en cours. |
500 |
server
|
L’écriture du PDF a échoué, du côté du serveur. | Cela vaut la peine de réessayer. Dites-le-nous si cela se répète. |
503 |
unreachable
|
Le serveur n’a pas pu être joint, ou ce site ignore où il se trouve. Rien de la description n’a été lu. | La seule réponse pour laquelle il vaut la peine de renvoyer la même requête. |
Un mot de plus peut arriver sous n’importe lequel des numéros ci-dessus : `other`. Il signifie que le serveur a refusé sans préciser de quel refus il s’agit, ce que fait un serveur plus ancien que ce mot. Lisez le numéro, et prévenez-nous.
Ce qui revient à côté du fichier
Une réussite contient trois décomptes. x-hqf-pages donne le nombre de pages du document fini, et ce nombre est consigné à côté du rendu ; l’appel compte pour un document, quelle que soit cette valeur. x-hqf-warnings donne le nombre de choses que le moteur a remarquées en écrivant. x-hqf-findings donne le nombre de constats qui séparent le document fini des normes que la clé check de votre description a nommées. Les trois figurent sur toute réussite, 0 compris ; une réponse qui n’en contient aucun vient d’un serveur plus ancien qu’eux.
Les remarques elles-mêmes viennent sous x-hqf-warning et les constats sous x-hqf-finding. Chaque nom n’est donné qu’une fois, ses valeurs séparées par une virgule : coupez d’abord sur la virgule, puis décodez chaque morceau — dans cet ordre, jamais l’inverse. Une virgule à l’intérieur d’une valeur s’écrit %2C, si bien que la coupure ne tombe jamais dans une valeur.
Chaque valeur est écrite sous la forme qu’un en-tête accepte : tout octet qui n’est pas un ASCII imprimable, et le signe pour cent lui-même, s’écrit %XX. Dix valeurs au plus sous chacun des deux noms, mille octets à elles toutes, la première des deux limites atteinte l’emportant ; la valeur qui dépasse ce qui reste est coupée et se termine par %E2%80%A6. Les comptes donnent de toute façon le nombre entier, si bien qu’un client qui lit x-hqf-warnings: 50 à côté de dix valeurs a quand même le compte exact.
Aucune réponse ne dit combien de temps le travail a pris, ici comme sur un serveur qui vous appartient : les deux durées que le service mesure partent dans son propre journal, et aucun en-tête ne vous les renvoie.
Ce que coûte un appel
- Un document est retiré du mois avant que la description ne soit transmise.
- Un rendu qui n’arrive jamais ne vous est pas compté : un appel qui reçoit 503 ne coûte rien.
- Un appel refusé avant que rien ne soit transmis — pas de clé d’API, un corps qui n’est pas du JSON, une description qui nomme un fichier absent du corps, un mois dont il ne reste rien — ne coûte rien non plus.
- Les pages inscrites sont celles que le serveur annonce ; ce site n’ouvre pas le document pour les recompter.
L’essayer à la main
La même adresse ouverte dans un navigateur affiche une zone de saisie : collez-y une description et le document revient sur-le-champ. Il crée le document pour la personne connectée, à partir de la session du navigateur plutôt que d’une clé d’API, et ce qu’il crée n’est pas décompté du quota du mois.
C’est la même adresse, la même description et le même serveur : ce que vous essayez à la main est ce que votre programme enverra. Le cadre ne prend que la description : la page n’offre nulle part où déposer un fichier, si bien qu’une description qui en nomme un s’essaie depuis votre propre programme.
Où aller ensuite
Le format d’une description Votre compte : ses clés de licence et ses factures Ce que devient un document que vous envoyez