개발자

Formalini API

문서와 추출된 필드를 조회하고, 자체 시스템에서 문서를 등록하며, 값을 수정하고, 변경 사항 발생 시 서명된 웹훅을 수신할 수 있습니다. 아래의 모든 예제는 curl, JavaScript, Python, PHP, C#으로 제공됩니다.

기본 URL

모든 엔드포인트는 이 기본 URL 아래에 위치합니다. JSON 형식으로 송수신합니다.

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

단축 주소인 api.formalini.com이 현재 운영 중이며, 위에 표시된 주소입니다.

인증

앱의 '연동(Integrations)' 메뉴에서 API 키를 생성한 후, 모든 요청에 Bearer 토큰으로 전송하세요. 키는 하나의 워크스페이스에 속하며 해당 워크스페이스의 데이터만 접근할 수 있습니다.

  • documents.read — 문서 및 필드 조회
  • documents.write — 문서 생성 및 필드 수정
  • workflows.run — 필요 시 웹훅으로 이벤트 전송

각 키에는 분당 요청 한도가 적용됩니다(기본값 60회). 응답 헤더에 X-RateLimit-Limit 및 X-RateLimit-Remaining이 포함되며, 한도 초과 시 Retry-After 헤더와 함께 429 응답이 반환됩니다.

문서 목록 조회

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

최신순으로 정렬됩니다. limit은 1–100(기본값 25)을 지원하며, status는 queued, processing, review, approved, exported, 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 }
      ]
    }
  ]
}

수정된 값이 원본 인식 값을 대체하므로, 항상 value 필드의 값을 사용해야 합니다.

문서 단건 조회

GET /documents/{id} — documents.read

카테고리, 공급업체, 금액, 날짜 및 모든 필드가 포함된 문서를 반환합니다.

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

파일 업로드 및 데이터 추출

POST /documents/upload — documents.write

페이지 이미지를 전송하면 동일한 응답에서 구조화된 데이터를 바로 반환합니다. 문서는 다른 스캔과 마찬가지로 워크벤치 받은편지함에 표시되며, 페이지당 AI 크레딧 1개가 차감되고 완료 시 웹훅을 트리거합니다.

  • file — 페이지 이미지입니다. 다중 페이지 문서의 경우 페이지당 한 번씩 필드를 반복하세요(최대 25개).
  • name — 선택 사항인 문서 이름입니다. 생략 시 파일 이름이 사용됩니다.
  • template_id — 선택 사항인 템플릿 ID입니다. 지정하면 사용자 정의 필드 이름으로 반환됩니다.
  • async — true로 설정하면 즉시 202 응답을 받고, 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, HEIC 형식을 지원하며 페이지당 최대 100MB입니다. 페이지 분석에는 몇 초가 걸리므로 넉넉한 타임아웃을 설정하거나 async=true를 사용하세요. 402 오류는 워크스페이스의 크레딧, 저장 공간 또는 일일 한도가 소진되었음을 의미하며 응답에 상세 원인이 표시됩니다.

현재 여기서는 PDF를 직접 지원하지 않습니다. 각 페이지를 JPEG 또는 PNG로 렌더링한 후 file 필드를 반복하여 전송하세요. 앱 내 PDF 업로드는 브라우저에서 페이지를 렌더링하므로 정상 작동합니다.

문서 생성

POST /documents — documents.write

이미 데이터가 있는 문서를 등록합니다. name은 필수이며, fields는 선택 사항으로 최대 400개까지 지정할 수 있습니다. fields가 있는 문서는 review 상태가 되고, 없는 문서는 queue 상태가 됩니다.

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

문서 수정

PATCH /documents/{id} — documents.write

상태를 변경하거나 이름별로 필드 값을 수정합니다. 상태를 approved, exported 또는 review로 설정하면 해당 웹훅도 트리거됩니다.

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 }

웹훅 트리거

POST /workflows/dispatch — workflows.run

구독 중인 모든 활성 웹훅으로 이벤트를 전송하여 필요할 때 다른 시스템을 실행할 수 있습니다. 허용 이벤트: 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…" }

웹훅

앱에서 웹훅 URL을 등록하고 수신할 이벤트를 선택하세요. 각 요청은 JSON 본문과 웹훅 시크릿으로 서명된 서명 헤더를 포함한 POST 방식으로 전송됩니다.

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

전송 실패 시 대기 시간을 늘려가며 최대 5회 재시도되며, 앱에서 언제든지 전송을 재실행할 수 있습니다.

요청을 신뢰하기 전에 원본 요청 본문(raw request body)을 기준으로 서명을 검증하세요.

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

오류

  • 400 — 요청 본문이 JSON이 아니거나, 필수 값이 누락되었거나 유효하지 않습니다
  • 401 — 키가 없거나 취소되었거나 만료되었습니다
  • 403 — 해당 엔드포인트에 필요한 권한이 키에 없습니다
  • 404 — 해당 워크스페이스에 해당 ID를 가진 문서가 없습니다
  • 429 — 분당 요청 한도에 도달했습니다. 잠시 후 다시 시도하세요
  • 500 — 서버 측 오류가 발생했습니다. 응답 메시지를 확인하세요