// REST API

LenderAnalyzer API Reference

A single REST API to upload documents, run AI extraction, and retrieve structured JSON — fields, line items and confidence scores — ready to stream into any application or data pipeline.

// 01

Authentication

Every request to the LenderAnalyzer API must carry a valid API key issued from your dashboard. Keys are scoped to your workspace and carry the permissions of the account that created them. Treat them as passwords — never commit them to source control.

Creating an API key

  1. 1 Sign in to your LenderAnalyzer dashboard.
  2. 2 Open Settings → API Keys.
  3. 3 Click Create API Key, give it a descriptive name, and confirm.
  4. 4 Copy the key immediately — it is only shown once.

Sending the header

HTTP header
Authorization: Bearer YOUR_API_KEY
// 02

Upload a Document

The first step in any extraction workflow is uploading the source file. LenderAnalyzer accepts PDF, PNG, JPG, TIFF and WEBP formats, including multi-page PDFs and high-resolution scans. The endpoint returns a file_id that you pass to the Extract endpoint.

POST /api/documents/upload

Request multipart/form-data

Field Type Required Description
document file required PDF, PNG, JPG, TIFF or WEBP. Max 50 MB.

Response 200 OK

POST /api/documents/upload → 200
{
  "success": true,
  "message": "File uploaded successfully",
  "data": {
    "file_id": 1234,
    "name": "invoice-q1-2026.pdf"
  }
}
// 03

Run Extraction

Kick off an AI extraction job for a previously uploaded file. LenderAnalyzer queues the job asynchronously and returns an extraction_hash immediately. Poll GET /api/documents/extraction/{hash} for the result, or configure a webhook to receive it automatically.

POST /api/documents/extract

Request body application/json

Field Type Required Description
file_id integer required The file_id returned by the upload endpoint.
document_type_id integer optional ID of a custom extraction template. Omit to let the AI auto-detect the document type.

Request body

POST /api/documents/extract
{
  "file_id": 1234,
  "document_type_id": 5678
}

Response 202

→ 202 Accepted
{
  "success": true,
  "message": "Extraction initialized",
  "data": {
    "extraction_id": 9876,
    "extraction_hash": "abc123-def456"
  }
}
// 04

Retrieve Extraction Results

Fetch the structured output for a completed extraction job. The response contains every extracted field with its value and page number, plus fully-parsed line-item tables where present. Each field carries a confidence score between 0 and 1 so downstream systems can route uncertain fields to human review.

GET /api/documents/extraction/{hash}

Path parameter

Param Description
hash The extraction_hash returned by POST /extract.

Response 200 OK — invoice example

GET /api/documents/extraction/abc123-def456 → 200
{
  "success": true,
  "data": {
    "extraction_id": 9876,
    "extraction_hash": "abc123-def456-ghi789-jkl012",
    "status": "completed",
    "confidence": 0.987,
    "file": {
      "id": 1234,
      "name": "invoice-q1-2026.pdf"
    },
    "document_type": {
      "id": 5678,
      "name": "Invoice"
    },
    "fields": [
      {
        "field_id": 101,
        "content": "vendor",
        "value": "Acme Supplies Ltd",
        "confidence": 0.998,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 102,
        "content": "document_number",
        "value": "INV-2026-0892",
        "confidence": 0.994,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 103,
        "content": "issue_date",
        "value": "2026-03-14",
        "confidence": 0.991,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 104,
        "content": "currency",
        "value": "USD",
        "confidence": 1.0,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 105,
        "content": "total",
        "value": "594.00",
        "confidence": 0.987,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 106,
        "content": "line_items",
        "is_table": true,
        "page": 1,
        "rows": [
          {
            "cells": [
              { "value": "Steel brackets", "is_header": false },
              { "value": "120",            "is_header": false },
              { "value": "4.50",          "is_header": false },
              { "value": "540.00",        "is_header": false }
            ]
          }
        ]
      }
    ]
  }
}

Extraction status values

Status Meaning
pending The job is queued and waiting for a worker.
processing The AI model is reading and extracting fields.
completed Extraction finished successfully. fields are populated.
failed An unrecoverable error occurred. Retry or contact support.
// 05

List Document Types

Return every extraction template available to your workspace — LenderAnalyzer built-in types and any custom templates your team has configured. Use the returned id as document_type_id in the Extract endpoint to target a specific field schema.

GET /api/documents/types

Response 200 OK

GET /api/documents/types → 200
{
  "success": true,
  "data": [
    {
      "id": 5678,
      "name": "Invoice",
      "description": "Vendor invoices and bills — all layouts",
      "is_public": true,
      "fields_count": 12
    },
    {
      "id": 5679,
      "name": "Purchase Order",
      "description": "Purchase orders with line-item tables",
      "is_public": false,
      "fields_count": 9
    },
    {
      "id": 5680,
      "name": "Bank Statement",
      "description": "Transactions, balances and account details",
      "is_public": true,
      "fields_count": 8
    }
  ]
}
// 06

Webhooks

Instead of polling for results, register a webhook URL in your dashboard and LenderAnalyzer will POST the completed extraction payload to your endpoint the moment the job finishes — typically within seconds of the document being processed. Webhooks eliminate polling overhead and are the recommended pattern for high-volume pipelines.

Registering a webhook endpoint

  1. 1 In your dashboard, open Settings → Webhooks.
  2. 2 Paste your HTTPS endpoint URL and click Save.
  3. 3 Copy the signing secret shown — it is used to verify payloads.
  4. 4 On every event LenderAnalyzer sends an X-LenderAnalyzer-Signature header (HMAC-SHA256 of the raw request body using your secret). Verify it before processing.

Webhook payload — extraction.completed

POST https://your-app.com/webhook/lenderanalyzer
{
  "event": "extraction.completed",
  "extraction_hash": "abc123-def456-ghi789-jkl012",
  "extraction_id": 9876,
  "status": "completed",
  "confidence": 0.987,
  "document_type": "Invoice",
  "fields": [
    { "content": "vendor",          "value": "Acme Supplies Ltd", "confidence": 0.998 },
    { "content": "document_number", "value": "INV-2026-0892",    "confidence": 0.994 },
    { "content": "total",           "value": "594.00",           "confidence": 0.987 }
  ]
}
// 07

Error Codes

LenderAnalyzer uses standard HTTP status codes. Every error response includes a machine-readable error_code string and a human-readable message to simplify debugging.

HTTP error_code When it happens
400 invalid_request Missing or malformed required field in the request body.
401 unauthorized No API key supplied, or the key is invalid or revoked.
403 forbidden Your plan does not include API access, or you hit a quota.
404 not_found The file_id or extraction_hash does not exist in your workspace.
415 unsupported_media The uploaded file format is not accepted (use PDF, PNG, JPG, TIFF, WEBP).
422 extraction_failed The AI could not extract meaningful data from the document.
429 rate_limit_exceeded Too many requests. Check Retry-After header for the cooldown window.
500 server_error An internal error occurred. Retry with exponential back-off.

Error response shape

→ 401 Unauthorized
{
  "success": false,
  "error_code": "unauthorized",
  "message": "Invalid or missing API key."
}
// 08

Rate Limits

Rate limits are applied per API key on a rolling 60-second window. If you exceed the limit the API returns HTTP 429 with a Retry-After header indicating the number of seconds to wait before retrying.

Plan Requests / hour Max file size Concurrent jobs
Pro 500 50 MB 10
Enterprise Unlimited 200 MB Configurable

Rate-limit response headers

Response headers on every call
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1748987200
Retry-After: 42   // only present on 429

Need higher throughput or custom SLAs? Talk to our team about an Enterprise plan.

// 09

Code Examples

Copy-paste snippets that cover the full extraction lifecycle — upload a document, trigger extraction, and retrieve the structured JSON result. Every example uses the same field names and extraction hash so you can follow the flow across languages.

shell — full extraction workflow
# 1 — Upload the document
curl -X POST https://lenderanalyzer.com/api/documents/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "document=@/path/to/invoice-q1-2026.pdf"

# → {"success":true,"data":{"file_id":1234,"name":"invoice-q1-2026.pdf"}}

# 2 — Start extraction (document_type_id optional)
curl -X POST https://lenderanalyzer.com/api/documents/extract \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file_id": 1234, "document_type_id": 5678}'

# → {"success":true,"data":{"extraction_id":9876,"extraction_hash":"abc123-def456"}}

# 3 — Retrieve structured results
curl -X GET https://lenderanalyzer.com/api/documents/extraction/abc123-def456 \
  -H "Authorization: Bearer YOUR_API_KEY"
extract.php
<?php

$apiKey     = 'YOUR_API_KEY';
$baseUrl    = 'https://lenderanalyzer.com/api';
$filePath   = '/path/to/invoice-q1-2026.pdf';

// 1 — Upload
$ch = curl_init("$baseUrl/documents/upload");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $apiKey"],
    CURLOPT_POSTFIELDS     => ['document' => new CURLFile($filePath)],
]);
$upload = json_decode(curl_exec($ch), true);
curl_close($ch);

if (!$upload['success']) {
    die("Upload failed: " . $upload['message']);
}
$fileId = $upload['data']['file_id'];

// 2 — Extract
$ch = curl_init("$baseUrl/documents/extract");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer $apiKey",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'file_id'          => $fileId,
        'document_type_id' => 5678, // optional
    ]),
]);
$extract = json_decode(curl_exec($ch), true);
curl_close($ch);

if (!$extract['success']) {
    die("Extraction failed: " . $extract['message']);
}
$hash = $extract['data']['extraction_hash'];

// 3 — Poll for results
do {
    sleep(2);
    $ch = curl_init("$baseUrl/documents/extraction/$hash");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["Authorization: Bearer $apiKey"],
    ]);
    $result = json_decode(curl_exec($ch), true);
    curl_close($ch);
} while ($result['data']['status'] === 'processing');

foreach ($result['data']['fields'] as $field) {
    echo $field['content'] . ': ' . $field['value'] . "\n";
}
extract.mjs
const API_KEY  = 'YOUR_API_KEY';
const BASE_URL = 'https://lenderanalyzer.com/api';

async function uploadDocument(file) {
  const form = new FormData();
  form.append('document', file);

  const res = await fetch(`${BASE_URL}/documents/upload`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${API_KEY}` },
    body: form,
  });
  const json = await res.json();
  if (!json.success) throw new Error(json.message);
  return json.data.file_id;
}

async function startExtraction(fileId, documentTypeId = null) {
  const payload = { file_id: fileId };
  if (documentTypeId) payload.document_type_id = documentTypeId;

  const res = await fetch(`${BASE_URL}/documents/extract`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
  });
  const json = await res.json();
  if (!json.success) throw new Error(json.message);
  return json.data.extraction_hash;
}

async function pollResults(hash, intervalMs = 2000) {
  while (true) {
    const res  = await fetch(`${BASE_URL}/documents/extraction/${hash}`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` },
    });
    const json = await res.json();
    if (json.data.status === 'completed') return json.data;
    if (json.data.status === 'failed')    throw new Error('Extraction failed');
    await new Promise(r => setTimeout(r, intervalMs));
  }
}

// Usage
const fileInput = document.querySelector('input[type="file"]');
const fileId    = await uploadDocument(fileInput.files[0]);
const hash      = await startExtraction(fileId, 5678);
const data      = await pollResults(hash);
console.log('Extracted fields:', data.fields);
extract.py
import time
import requests

API_KEY  = "YOUR_API_KEY"
BASE_URL = "https://lenderanalyzer.com/api"
HEADERS  = {"Authorization": f"Bearer {API_KEY}"}

def upload_document(file_path: str) -> int:
    with open(file_path, "rb") as f:
        resp = requests.post(
            f"{BASE_URL}/documents/upload",
            headers=HEADERS,
            files={"document": f},
        )
    data = resp.json()
    if not data["success"]:
        raise RuntimeError(data["message"])
    return data["data"]["file_id"]

def start_extraction(file_id: int, document_type_id: int | None = None) -> str:
    payload = {"file_id": file_id}
    if document_type_id:
        payload["document_type_id"] = document_type_id
    resp = requests.post(
        f"{BASE_URL}/documents/extract",
        headers={**HEADERS, "Content-Type": "application/json"},
        json=payload,
    )
    data = resp.json()
    if not data["success"]:
        raise RuntimeError(data["message"])
    return data["data"]["extraction_hash"]

def poll_results(extraction_hash: str, interval: float = 2.0) -> dict:
    url = f"{BASE_URL}/documents/extraction/{extraction_hash}"
    while True:
        data = requests.get(url, headers=HEADERS).json()["data"]
        if data["status"] == "completed":
            return data
        if data["status"] == "failed":
            raise RuntimeError("Extraction failed")
        time.sleep(interval)

# Usage
file_id  = upload_document("/path/to/invoice-q1-2026.pdf")
ex_hash  = start_extraction(file_id, document_type_id=5678)
result   = poll_results(ex_hash)

for field in result["fields"]:
    print(f"{field['content']}: {field['value']}  (confidence={field['confidence']})")

Ready to wire up document extraction?

Generate your first API key and start pulling structured data from your documents in minutes.