Dezvoltatori

API Formalini

Citește documente și câmpurile extrase, trimite documente din sistemele tale, corectează valori și primește webhookuri semnate când se schimbă ceva. Fiecare exemplu apare în curl, JavaScript, Python, PHP și C#.

URL de bază

Toate endpointurile sunt sub acest URL de bază. Se trimite și se primește JSON.

https://api.formalini.com/api/public/v1

Adresa scurtă api.formalini.com este activă; este cea afișată mai sus.

Autentificare

Creează o cheie API în aplicație, la Integrări, și trimite-o ca bearer token la fiecare cerere. O cheie aparține unui singur spațiu și vede doar datele acelui spațiu.

  • documents.read — citește documente și câmpurile lor
  • documents.write — creează documente și corectează câmpuri
  • workflows.run — trimite evenimente către webhookurile tale la cerere

Fiecare cheie are o limită pe minut (60 implicit). Răspunsurile conțin X-RateLimit-Limit și X-RateLimit-Remaining; peste limită primești 429 cu Retry-After.

Listează documente

GET /documents?limit=25&status=approved — documents.read

Cele mai noi primele. limit acceptă 1–100 (25 implicit), iar status filtrează după queued, processing, review, approved, exported sau 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 }
      ]
    }
  ]
}

Valorile corectate înlocuiesc citirea iniţială, deci value este mereu valoarea de folosit.

Obține un document

GET /documents/{id} — documents.read

Întoarce documentul cu categoria, furnizorul, suma, data și toate câmpurile.

curl -X GET "https://api.formalini.com/api/public/v1/documents/b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90" \
  -H "Authorization: Bearer pf_live_your_key"

Încarcă un fișier și primești datele

POST /documents/upload — documents.write

Trimite imaginea unei pagini și primești datele structurate în același răspuns. Documentul apare în atelier ca orice altă scanare, consumă un credit AI pe pagină și declanșează webhook-urile tale la final.

  • file — imaginea paginii; repetă câmpul o dată pentru fiecare pagină (maxim 25) la documente cu mai multe pagini
  • name — nume opțional pentru document; dacă lipsește se folosește numele fișierului
  • template_id — id opțional de șablon, ca să primești câmpurile cu denumirile tale
  • async — setează true ca să primești imediat 202 și să interoghezi 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 }
    ]
  }
}

Se accept JPEG, PNG, WebP și HEIC, până la 100 MB pe pagină. Citirea unei pagini durează câteva secunde: folosește un timeout generos sau async=true. Un 402 înseamnă că spațiul a rămas fără credite, fără stocare sau fără alocația zilnică, iar răspunsul spune care.

PDF-urile nu sunt acceptate încă aici: transformă fiecare pagină în JPEG sau PNG și trimite-le repetând câmpul file. Încărcarea unui PDF în aplicație funcționează în continuare, pentru că paginile sunt convertite în browser.

Creează un document

POST /documents — documents.write

Trimite un document pentru care ai deja date. name este obligatoriu; fields este opțional și acceptă până la 400 de intrări. Cu câmpuri ajunge în revizuire, fără câmpuri în coadă.

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
  }
}

Actualizează un document

PATCH /documents/{id} — documents.write

Schimbă starea sau corectează valori după nume. Starea approved, exported sau review declanșează și webhookul potrivit.

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 }

Declanșează webhookurile

POST /workflows/dispatch — workflows.run

Trimite un eveniment către toate webhookurile active abonate la el, ca să pornești alt sistem la cerere. Evenimente permise: 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…" }

Webhookuri

Adaugă un URL de webhook în aplicație și alege evenimentele. Fiecare livrare este un POST cu corp JSON și un header de semnătură, semnat cu secretul tău.

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": { … } }

Livrările eșuate se reîncearcă de până la cinci ori cu întârziere crescătoare, și poți retrimite orice livrare din aplicație.

Verifică semnătura față de corpul brut înainte să ai încredere în livrare:

// 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=", "")));

Erori

  • 400 — corpul nu este JSON, sau lipsește ori este invalidă o valoare obligatorie
  • 401 — fără cheie, sau cheia este revocată ori expirată
  • 403 — cheia nu are permisiunea cerută de acest endpoint
  • 404 — nu există document cu acel id în acest spațiu
  • 429 — limita pe minut atinsă – așteaptă și reîncearcă
  • 500 — ceva a eșuat la noi; răspunsul spune ce