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.
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.
Authorization: Bearer YOUR_API_KEY
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.
/api/documents/upload
| Field | Type | Required | Description |
|---|---|---|---|
| document | file | required | PDF, PNG, JPG, TIFF or WEBP. Max 50 MB. |
{
"success": true,
"message": "File uploaded successfully",
"data": {
"file_id": 1234,
"name": "invoice-q1-2026.pdf"
}
}
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.
/api/documents/extract
| 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. |
{
"file_id": 1234,
"document_type_id": 5678
}
{
"success": true,
"message": "Extraction initialized",
"data": {
"extraction_id": 9876,
"extraction_hash": "abc123-def456"
}
}
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.
/api/documents/extraction/{hash}
| Param | Description |
|---|---|
| hash | The extraction_hash returned by POST /extract. |
{
"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 }
]
}
]
}
]
}
}
| 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. |
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.
/api/documents/types
{
"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
}
]
}
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.
{
"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 }
]
}
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. |
{
"success": false,
"error_code": "unauthorized",
"message": "Invalid or missing API key."
}
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 |
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.
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.
# 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"
<?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"; }
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);
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']})")
Generate your first API key and start pulling structured data from your documents in minutes.