API
Tout ce que fait le site se fait aussi en HTTP. Une clé, une requête, un identifiant de travail à interroger. Les exemples ci-dessous sont copiables tels quels : remplacez la clé, ils fonctionnent.
Toutes nos clés commencent par prk_. Ce préfixe permet aux détecteurs de secrets de GitHub de vous prévenir si l'une d'elles est publiée par erreur.
Démarrer en deux minutes
Aucune clé n'est nécessaire. C'est le premier appel à tenter quand quelque chose ne va pas.
curl https://pdfremakeit.app/v1/statusconst r = await fetch("https://pdfremakeit.app/v1/status");
console.log(await r.json());
// { "status": "operational", "capacity": "normal" }import urllib.request, json
with urllib.request.urlopen("https://pdfremakeit.app/v1/status") as r:
print(json.load(r))
# {'status': 'operational', 'capacity': 'normal'}Authentification
Chaque appel porte votre clé dans l'en-tête Authorization. La clé vaut mot de passe : elle ne doit jamais partir dans du code exécuté par le navigateur, ni dans un dépôt public.
Adresse de base : https://pdfremakeit.app
Authorization: Bearer prk_…Clés d'API
Une clé par usage : un environnement compromis se révoque seul.
Vous pouvez avoir jusqu'à 10 clés actives.
Derniers appels
Aucun appel enregistré. Les appels apparaissent ici quelques secondes après leur exécution.
Le détail des appels est conservé trente jours.
Points d'entrée
| Appel | Clé | Effet |
|---|---|---|
GET /v1 | — | Liste des points d'entrée. |
GET /v1/status | — | État du service et capacité. |
GET /v1/tools | — | Catalogue des outils serveur et formats acceptés. |
GET /v1/account | oui | Formule, limites et consommation. |
GET /v1/logs | oui | Derniers appels de vos clés. |
POST /v1/tools/{outil} | oui | Soumet un document. Rend un travail. |
GET /v1/jobs/{id} | oui | État d'un travail. |
GET /v1/jobs/{id}/content | oui | Télécharge le résultat. |
GET /v1/workflows | oui | Vos enchaînements enregistrés. |
POST /v1/workflows | oui | Enregistre un enchaînement. |
DELETE /v1/workflows/{id} | oui | Supprime un enchaînement. Les exécutions passées restent. |
POST /v1/workflows/{id}/run | oui | Lance un enchaînement sur un document. |
GET /v1/runs | oui | Vos dernières exécutions. |
GET /v1/runs/{id} | oui | État d'une exécution, étape par étape. |
GET /v1/runs/{id}/content | oui | Télécharge le résultat final. |
POST /v1/runs/{id}/replay | oui | Rejoue une exécution sur le même document. |
POST /v1/ai/summarize | oui | Résume un texte. |
POST /v1/ai/translate | oui | Traduit un texte. |
Convertir un document en PDF
Le document part dans le corps de la requête, brut. Le nom d'origine voyage dans l'en-tête X-Source-Name : c'est son extension qui décide du traitement.
# 1. Envoyer le document. La réponse porte l'identifiant du travail.
curl -X POST https://pdfremakeit.app/v1/tools/office \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "X-Source-Name: rapport.docx" \
--data-binary @rapport.docx
# 2. Interroger le travail jusqu'à "done".
curl https://pdfremakeit.app/v1/jobs/JOB_ID \
-H "Authorization: Bearer prk_VOTRE_CLE"
# 3. Récupérer le PDF.
curl -L https://pdfremakeit.app/v1/jobs/JOB_ID/content \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-o rapport.pdfimport { readFile, writeFile } from "node:fs/promises";
const CLE = "prk_VOTRE_CLE";
const entetes = { Authorization: `Bearer ${CLE}` };
const depart = await fetch("https://pdfremakeit.app/v1/tools/office", {
method: "POST",
headers: { ...entetes, "X-Source-Name": "rapport.docx" },
body: await readFile("rapport.docx"),
});
const { id } = await depart.json();
// Un travail serveur est asynchrone : on interroge jusqu'à ce qu'il aboutisse.
let etat;
do {
await new Promise((r) => setTimeout(r, 1500));
etat = await (await fetch(`https://pdfremakeit.app/v1/jobs/${id}`, { headers: entetes })).json();
} while (etat.status === "queued" || etat.status === "running");
if (etat.status === "error") throw new Error(etat.error);
const pdf = await fetch(`https://pdfremakeit.app/v1/jobs/${id}/content`, { headers: entetes });
await writeFile("rapport.pdf", Buffer.from(await pdf.arrayBuffer()));import time, requests
CLE = "prk_VOTRE_CLE"
entetes = {"Authorization": f"Bearer {CLE}"}
with open("rapport.docx", "rb") as f:
depart = requests.post(
"https://pdfremakeit.app/v1/tools/office",
headers={**entetes, "X-Source-Name": "rapport.docx"},
data=f,
)
depart.raise_for_status()
identifiant = depart.json()["id"]
# Un travail serveur est asynchrone : on interroge jusqu'à ce qu'il aboutisse.
while True:
time.sleep(1.5)
etat = requests.get(f"https://pdfremakeit.app/v1/jobs/{identifiant}", headers=entetes).json()
if etat["status"] not in ("queued", "running"):
break
if etat["status"] == "error":
raise RuntimeError(etat["error"])
pdf = requests.get(f"https://pdfremakeit.app/v1/jobs/{identifiant}/content", headers=entetes)
open("rapport.pdf", "wb").write(pdf.content)Reconnaître le texte d'un scan
Même déroulé que la conversion : seuls l'outil et ses réglages changent. La langue se passe en paramètre d'adresse.
curl -X POST "https://pdfremakeit.app/v1/tools/ocr?langues=fra" \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "X-Source-Name: scan.pdf" \
--data-binary @scan.pdfconst depart = await fetch("https://pdfremakeit.app/v1/tools/ocr?langues=fra", {
method: "POST",
headers: {
Authorization: `Bearer ${CLE}`,
"X-Source-Name": "scan.pdf",
},
body: await readFile("scan.pdf"),
});
const { id, links } = await depart.json();
console.log(links.self); // /v1/jobs/...with open("scan.pdf", "rb") as f:
depart = requests.post(
"https://pdfremakeit.app/v1/tools/ocr",
params={"langues": "fra"},
headers={**entetes, "X-Source-Name": "scan.pdf"},
data=f,
)
print(depart.json()["id"])Résumer un texte
Le résumé ne prend pas de fichier mais du texte : extrayez-le d'abord, par exemple avec l'outil OCR ci-dessus, ou depuis votre propre source.
curl -X POST https://pdfremakeit.app/v1/ai/summarize \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"text": "Le rapport annuel 2025 fait état d un chiffre d affaires de 184 000 euros...",
"length": "short",
"format": "bullets"
}'const r = await fetch("https://pdfremakeit.app/v1/ai/summarize", {
method: "POST",
headers: {
Authorization: `Bearer ${CLE}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, length: "short", format: "bullets" }),
});
const { text: resume, partial } = await r.json();
// `partial` vaut true si le traitement s'est interrompu : le texte reste
// utilisable, mais il est incomplet.
console.log(resume);r = requests.post(
"https://pdfremakeit.app/v1/ai/summarize",
headers={**entetes, "Content-Type": "application/json"},
json={"text": texte, "length": "short", "format": "bullets"},
)
resultat = r.json()
# `partial` vaut True si le traitement s'est interrompu : le texte reste
# utilisable, mais il est incomplet.
print(resultat["text"])Traduire au fil de l'eau
Avec "stream": true, la réponse est un flux NDJSON : un objet JSON par ligne. C'est ce qui permet d'afficher le texte pendant qu'il s'écrit.
curl -N -X POST https://pdfremakeit.app/v1/ai/translate \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{"text": "Bonjour le monde.", "targetLanguage": "en", "stream": true}'
# {"t":"debut","blocs":1,...}
# {"t":"texte","v":"Hello"}
# {"t":"texte","v":" world."}
# {"t":"fin","caracteres":12}const r = await fetch("https://pdfremakeit.app/v1/ai/translate", {
method: "POST",
headers: {
Authorization: `Bearer ${CLE}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, targetLanguage: "en", stream: true }),
});
const lecteur = r.body.pipeThrough(new TextDecoderStream()).getReader();
let reste = "";
for (;;) {
const { done, value } = await lecteur.read();
if (done) break;
reste += value;
let saut;
while ((saut = reste.indexOf("\n")) !== -1) {
const ligne = reste.slice(0, saut);
reste = reste.slice(saut + 1);
if (!ligne) continue;
const evenement = JSON.parse(ligne);
if (evenement.t === "texte") process.stdout.write(evenement.v);
}
}import json, requests
with requests.post(
"https://pdfremakeit.app/v1/ai/translate",
headers={**entetes, "Content-Type": "application/json"},
json={"text": texte, "targetLanguage": "en", "stream": True},
stream=True,
) as r:
for ligne in r.iter_lines():
if not ligne:
continue
evenement = json.loads(ligne)
if evenement["t"] == "texte":
print(evenement["v"], end="", flush=True)Enchaîner plusieurs outils
Un workflow s'enregistre une fois, puis se lance d'un seul appel : le résultat de chaque étape devient l'entrée de la suivante. Réservé aux formules payantes. Cinq étapes au plus, et seule la première peut refuser le PDF.
# 1. Enregistrer l'enchaînement. Rendu une fois pour toutes.
curl -X POST https://pdfremakeit.app/v1/workflows \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{"name":"Courrier scanné",
"steps":[{"tool":"ocr","settings":{"langues":"fra"}},
{"tool":"pdfa","settings":{"niveau":"2b"}}]}'
# 2. Le lancer sur un document. Le quota des deux étapes est réservé ici.
curl -X POST https://pdfremakeit.app/v1/workflows/WORKFLOW_ID/run \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-H "X-Source-Name: courrier.pdf" \
--data-binary @courrier.pdf
# 3. Suivre, puis récupérer le PDF de la dernière étape.
curl https://pdfremakeit.app/v1/runs/RUN_ID -H "Authorization: Bearer prk_VOTRE_CLE"
curl -L https://pdfremakeit.app/v1/runs/RUN_ID/content \
-H "Authorization: Bearer prk_VOTRE_CLE" \
-o archive.pdfimport { readFile, writeFile } from "node:fs/promises";
const chaine = await (
await fetch("https://pdfremakeit.app/v1/workflows", {
method: "POST",
headers: { ...entetes, "Content-Type": "application/json" },
body: JSON.stringify({
name: "Courrier scanné",
steps: [
{ tool: "ocr", settings: { langues: "fra" } },
{ tool: "pdfa", settings: { niveau: "2b" } },
],
}),
})
).json();
const depart = await fetch(`https://pdfremakeit.app/v1/workflows/${chaine.id}/run`, {
method: "POST",
headers: { ...entetes, "X-Source-Name": "courrier.pdf" },
body: await readFile("courrier.pdf"),
});
let execution = await depart.json();
// Une étape peut durer plusieurs minutes : on interroge, on n'attend pas.
while (execution.status === "running") {
await new Promise((f) => setTimeout(f, 3000));
execution = await (
await fetch(`https://pdfremakeit.app/v1/runs/${execution.id}`, { headers: entetes })
).json();
const etape = Math.min(execution.step + 1, execution.totalSteps);
console.log(`étape ${etape} / ${execution.totalSteps}`);
}
if (execution.status !== "done") throw new Error(execution.error);
const pdf = await fetch(`https://pdfremakeit.app/v1/runs/${execution.id}/content`, {
headers: entetes,
});
await writeFile("archive.pdf", Buffer.from(await pdf.arrayBuffer()));import time, requests
chaine = requests.post(
"https://pdfremakeit.app/v1/workflows",
headers={**entetes, "Content-Type": "application/json"},
json={
"name": "Courrier scanné",
"steps": [
{"tool": "ocr", "settings": {"langues": "fra"}},
{"tool": "pdfa", "settings": {"niveau": "2b"}},
],
},
).json()
with open("courrier.pdf", "rb") as f:
execution = requests.post(
f"https://pdfremakeit.app/v1/workflows/{chaine['id']}/run",
headers={**entetes, "X-Source-Name": "courrier.pdf"},
data=f,
).json()
while execution["status"] == "running":
time.sleep(3)
execution = requests.get(
f"https://pdfremakeit.app/v1/runs/{execution['id']}", headers=entetes
).json()
etape = min(execution["step"] + 1, execution["totalSteps"])
print(f"etape {etape} / {execution['totalSteps']}")
if execution["status"] != "done":
raise SystemExit(execution["error"])
pdf = requests.get(
f"https://pdfremakeit.app/v1/runs/{execution['id']}/content", headers=entetes
)
open("archive.pdf", "wb").write(pdf.content)Rejouer une exécution
Sans renvoyer le document : il est encore chez nous tant que court la conservation de votre formule. Passé ce délai, l'appel répond source_expired.
curl -X POST https://pdfremakeit.app/v1/runs/RUN_ID/replay \
-H "Authorization: Bearer prk_VOTRE_CLE"const rejeu = await fetch(`https://pdfremakeit.app/v1/runs/${execution.id}/replay`, {
method: "POST",
headers: entetes,
});
if (rejeu.status === 410) {
// Le document d'origine a été purgé : il faut le renvoyer.
console.log("source expirée, relancer le workflow avec le fichier");
} else {
console.log("nouvelle exécution :", (await rejeu.json()).id);
}rejeu = requests.post(
f"https://pdfremakeit.app/v1/runs/{execution['id']}/replay", headers=entetes
)
if rejeu.status_code == 410:
print("source expiree, relancer le workflow avec le fichier")
else:
print("nouvelle execution :", rejeu.json()["id"])Lire son quota
À appeler avant une série d'appels plutôt qu'après un refus : `resetsAt` dit à quelle heure le compteur repart.
curl https://pdfremakeit.app/v1/account \
-H "Authorization: Bearer prk_VOTRE_CLE"const compte = await (
await fetch("https://pdfremakeit.app/v1/account", { headers: entetes })
).json();
const { used, limit } = compte.usage.serverJobs;
console.log(`${used} / ${limit} travaux aujourd'hui`);compte = requests.get("https://pdfremakeit.app/v1/account", headers=entetes).json()
usage = compte["usage"]["serverJobs"]
print(f"{usage['used']} / {usage['limit']} travaux aujourd'hui")Erreurs
Toutes les erreurs ont la même forme. Testez le code, jamais le message : le code fait partie du contrat, le message peut être réécrit.
{
"error": {
"code": "quota_exceeded",
"message": "The daily job quota for this account is exhausted.",
"plan": "libre",
"used": 3,
"limit": 3,
"resetsAt": 1787011200000,
"documentation": "https://pdfremakeit.app/api#quota_exceeded"
}
}| code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
missing_api_key | 401 | Aucune clé envoyée. | Ajouter l'en-tête Authorization: Bearer. |
invalid_api_key | 401 | Clé inconnue ou révoquée. | Vérifier la clé, ou en créer une nouvelle. |
rate_limited | 429 | Trop d'appels sur la minute. | Attendre la durée annoncée par l'en-tête Retry-After, puis reprendre. |
quota_exceeded | 429 | Quota du jour epuise. | Reprendre à l'heure donnée par resetsAt, ou changer de formule. |
too_many_concurrent_jobs | 429 | Trop de travaux en cours en même temps. | Attendre qu'un travail aboutisse avant d'en lancer un autre. |
unknown_tool | 404 | Outil inexistant. | Voir GET /v1/tools. |
unsupported_input | 415 | L'outil n'accepte pas cette extension. | Consulter le champ accepts de la réponse. |
missing_filename | 400 | Nom d'origine absent. | Ajouter X-Source-Name, ou ?filename=. |
empty_body | 400 | Corps de requête vide. | Envoyer le document en corps brut. |
file_too_large | 413 | Fichier au-delà de la limite. | Voir maxUploadBytes dans la réponse. |
invalid_request | 400 | Corps JSON mal formé. | Vérifier le champ text. |
text_too_short | 400 | Texte trop court — souvent un scan sans couche de texte. | Passer le document à l'OCR d'abord. |
text_too_long | 413 | Texte au-delà de la limite. | Découper le document et recombiner les résultats. |
unsupported_language | 400 | Langue cible hors du domaine du modèle. | Voir le champ supported de la réponse. |
job_not_ready | 409 | Le travail n'a pas encore abouti. | Interroger /v1/jobs/{id} jusqu'à « done ». |
result_expired | 410 | Résultat purgé. | Relancer le travail ; voir la rétention de votre formule. |
plan_required | 403 | Les workflows sont réservés aux formules payantes. | Changer de formule, ou lancer les outils un par un. |
invalid_workflow | 400 | Enchaînement refusé : étape inconnue, trop d'étapes, ou outil qui n'accepte pas le PDF ailleurs qu'en première position. | Voir le champ details, qui nomme l'étape fautive. |
too_many_workflows | 409 | Nombre maximal d'enchaînements atteint. | Supprimer un enchaînement devenu inutile. |
source_expired | 410 | Le document d'origine a été purgé : il n'y a plus rien à rejouer. | Relancer le workflow en renvoyant le fichier. |
not_found | 404 | Chemin ou travail inexistant. | Vérifier l'identifiant. |
service_capacity | 503 | Service suspendu, le temps d'absorber un afflux. | Réessayer plus tard ; les formules payantes passent en premier. |
internal_error | 500 | Panne de notre côté. | Rien n'a été décompté : réessayer. |
Quotas et débit
Une clé consomme le quota de son compte : passer par l'API ne double pas ce à quoi vous avez droit. Le débit, lui, se compte par clé — vos environnements ne se gênent pas entre eux.
- 60 requêtes par minute et par clé.
- Formule gratuite : 3 travaux et 20 000 caractères par jour.
- Formule Pro : 3 000 travaux et 20 000 000 caractères par jour.
Bibliothèques
Les deux bibliothèques couvrent l'attente d'un travail, les erreurs typées et le flux mot à mot. Elles ne sont pas obligatoires : l'API se consomme très bien avec curl.
npm install @pdfremakeit/sdkpip install pdfremakeit