Développeurs
API Formalini
Lisez les documents et leurs champs extraits, envoyez des documents depuis vos systèmes, corrigez des valeurs et recevez des webhooks signés à chaque changement. Chaque exemple est donné en curl, JavaScript, Python, PHP et C#.
URL de base
Tous les endpoints se trouvent sous cette URL de base. On envoie et reçoit du JSON.
https://api.formalini.com/api/public/v1L'adresse courte api.formalini.com est active ; c'est celle indiquée ci-dessus.
Authentification
Créez une clé API dans l'application, rubrique Intégrations, puis envoyez-la en bearer token à chaque requête. Une clé appartient à un seul espace et ne voit que ses données.
documents.read— lire les documents et leurs champsdocuments.write— créer des documents et corriger des champsworkflows.run— envoyer des événements à vos webhooks à la demande
Chaque clé a une limite par minute (60 par défaut). Les réponses portent X-RateLimit-Limit et X-RateLimit-Remaining ; au-delà vous recevez 429 avec Retry-After.
Lister les documents
GET /documents?limit=25&status=approved — documents.read
Les plus récents d'abord. limit accepte 1–100 (25 par défaut) et status filtre sur queued, processing, review, approved, exported ou failed.
curl -X GET "https://api.formalini.com/api/public/v1/documents?limit=25&status=approved" \
-H "Authorization: Bearer pf_live_your_key"{
"documents": [
{
"id": "b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90",
"name": "Job sheet 4821.jpg",
"status": "approved",
"average_confidence": 0.94,
"source": "upload",
"created_at": "2026-09-11T09:12:04.000Z",
"fields": [
{ "name": "engineer", "value": "M. Byrne", "type": "text",
"confidence": 0.97, "page": 1, "valid": true }
]
}
]
}Les valeurs corrigées remplacent la lecture initiale : value est toujours la valeur à utiliser.
Obtenir un document
GET /documents/{id} — documents.read
Renvoie le document avec sa catégorie, son fournisseur, son montant, sa date et tous ses champs.
curl -X GET "https://api.formalini.com/api/public/v1/documents/b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90" \
-H "Authorization: Bearer pf_live_your_key"Envoyer un fichier et récupérer ses données
POST /documents/upload — documents.write
Envoyez l'image d'une page et recevez les données structurées dans la même réponse. Le document apparaît dans l'atelier comme tout autre scan, consomme un crédit IA par page et déclenche vos webhooks à la fin.
file— l'image de la page ; répétez le champ une fois par page (jusqu'à 25) pour un document multipagename— nom de document facultatif ; sinon le nom du fichier est utilisétemplate_id— identifiant de modèle facultatif, pour retrouver vos propres noms de champsasync— mettez true pour recevoir un 202 immédiat et interroger GET /documents/{id}
curl -X POST "https://api.formalini.com/api/public/v1/documents/upload" \
-H "Authorization: Bearer pf_live_your_key" \
-F "file=@invoice-page-1.jpg" \
-F "file=@invoice-page-2.jpg" \
-F "name=Invoice 2026-0041"{
"document": {
"id": "8c2a1f76-4d33-4a1e-8f10-72b9e5c4a118",
"name": "Invoice 2026-0041",
"status": "review",
"average_confidence": 0.93,
"vendor": "Northbridge Supplies",
"amount": 1249.5,
"currency": "EUR",
"document_date": "2026-09-14",
"pages": 2,
"credits_spent": 2,
"fields": [
{ "name": "invoice_number", "value": "2026-0041", "type": "string",
"confidence": 0.98, "page": 1, "valid": true }
]
}
}JPEG, PNG, WebP et HEIC sont acceptés, jusqu'à 100 Mo par page. La lecture d'une page prend quelques secondes : prévoyez un délai généreux ou utilisez async=true. Un 402 signifie que l'espace n'a plus de crédits, de stockage ou d'allocation quotidienne, et la réponse précise lequel.
Les PDF ne sont pas encore acceptés ici : convertissez chaque page en JPEG ou PNG et envoyez-les en répétant le champ file. L'envoi d'un PDF dans l'application fonctionne toujours, car les pages sont converties dans votre navigateur.
Créer un document
POST /documents — documents.write
Envoyez un document dont vous avez déjà les données. name est obligatoire ; fields est facultatif et accepte jusqu'à 400 entrées. Avec des champs il part en revue, sans champs dans la file.
curl -X POST "https://api.formalini.com/api/public/v1/documents" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice 2026-0041.pdf",
"category": "invoice",
"vendor": "Northbridge Supplies",
"amount": 1249.5,
"document_date": "2026-09-14",
"fields": [
{
"name": "invoice_number",
"value": "2026-0041",
"type": "text",
"confidence": 0.99,
"page": 1
},
{
"name": "total",
"value": "1249.50",
"type": "number",
"confidence": 0.96,
"page": 1
}
]
}'{
"document": {
"id": "8c2a…",
"name": "Invoice 2026-0041.pdf",
"status": "review",
"created_at": "2026-09-18T10:02:11.000Z",
"fields": 2
}
}Mettre à jour un document
PATCH /documents/{id} — documents.write
Changez le statut ou corrigez des valeurs par nom. Les statuts approved, exported et review déclenchent aussi le webhook correspondant.
curl -X PATCH "https://api.formalini.com/api/public/v1/documents/b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"status": "approved",
"fields": [
{
"name": "total",
"value": "1249.50"
}
]
}'{ "ok": true, "updated_fields": 1 }Déclencher vos webhooks
POST /workflows/dispatch — workflows.run
Envoie un événement à tous les webhooks actifs qui y sont abonnés, pour lancer un autre système à la demande. Événements permis : document.processed, document.needs_review, document.approved, document.exported.
curl -X POST "https://api.formalini.com/api/public/v1/workflows/dispatch" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"event": "document.approved",
"document_id": "b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90",
"payload": {
"target": "erp"
}
}'{ "ok": true, "event": "document.approved", "document_id": "b0f1…" }Webhooks
Ajoutez une URL de webhook dans l'application et choisissez les événements. Chaque envoi est un POST avec un corps JSON et un en-tête de signature, signé avec votre secret.
POST https://your-app.example.com/hooks/formalini
Content-Type: application/json
X-Paperflow-Signature: sha256=<hex hmac of the raw body>
{ "event": "document.processed", "document_id": "b0f1…", "payload": { … } }Les envois en échec sont réessayés jusqu'à cinq fois avec un délai croissant, et vous pouvez rejouer n'importe quel envoi depuis l'application.
Vérifiez la signature sur le corps brut avant de faire confiance à un envoi :
// Node.js
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const got = (header || "").replace(/^sha256=/, "");
return got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
# Python
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, (header or "").removeprefix("sha256="))
// PHP
$expected = hash_hmac('sha256', $rawBody, $secret);
$got = str_replace('sha256=', '', $_SERVER['HTTP_X_PAPERFLOW_SIGNATURE'] ?? '');
$ok = hash_equals($expected, $got);
// C#
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(rawBodyBytes)).ToLowerInvariant();
var ok = CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(header.Replace("sha256=", "")));Erreurs
400— le corps n'est pas du JSON, ou une valeur obligatoire manque ou est invalide401— pas de clé, ou clé révoquée ou expirée403— la clé n'a pas le droit exigé par cet endpoint404— aucun document avec cet id dans cet espace429— limite par minute atteinte — attendez et réessayez500— quelque chose a échoué chez nous ; la réponse le précise