Automated Document & Invoice Intelligence Pipeline
Extract, validate, audit, and reconcile financial documents—invoices, purchase orders, receipts, and freight bills—with sub-cent unit costs, structured schema validation, and air-gapped mathematical verification.
This production blueprint orchestrates FOTOhub's Document Intelligence Engine (server/api-server/app/routes/textract.py) backed by AWS Textract list-price pass-through and Firecracker MicroVM Compute Sandboxes (server/agent-compute/app/routes_sandbox.py) to deliver 99.9% extraction accuracy without manual human data entry.
Architectural Workflow
flowchart TD
A["Raw Ingest: PDF / TIFF / PNG / JPEG (max 10 MB)"] --> B["Document Base64 Serialization / BYOB S3 Reference"]
B --> C{"Document Classification"}
C -->|"Invoices & Receipts"| D["Analyze Expense API (/v1/ai/document/analyze-expense)"]
C -->|"Complex Tables & Forms"| E["Analyze Document API (/v1/ai/document/analyze)"]
C -->|"Standard Text Extraction"| F["Main OCR API (/v1/documents/extract)"]
D & E & F --> G["Raw Geometry & Block Hierarchy"]
G --> H["PII Redaction Engine (/v1/documents/redact)"]
H --> I["Pydantic Structural Model Validation"]
I --> J["Firecracker MicroVM Sandbox (/sandbox/exec-python)"]
subgraph "Air-Gapped MicroVM Sandbox"
J1["Isolated Python Runtime (virtio-vsock, no eth0)"]
J2["Line-Item Cross-Multiplication: Qty × Price == Net"]
J3["Tax Bracket Audit: Net × TaxRate == TaxAmount"]
J4["Subtotal & Grand Total Reconciliation"]
J5["Anomaly & Duplicate Detection"]
J1 --> J2 --> J3 --> J4 --> J5
end
J --> K{"Reconciliation Audit Passed?"}
K -->|"Discrepancy Detected"| L["Flag for Human Review / Exception Queue / DLQ"]
K -->|"Verified (Delta == 0.00)"| M["Signed Webhook Notification (X-FotoHub-Signature)"]
M --> N["ERP Ingest: SAP S/4HANA / NetSuite / QuickBooks / Xero"]
M --> O["Batch Results to BYOB S3/R2 Bucket"]Supported Document Types & Formats
The FOTOhub Document Intelligence pipeline supports a wide array of document formats for both synchronous and asynchronous batch processing.
| Format | Extensions | Max File Size (Sync) | Max File Size (Batch) | Supported Endpoints | Notes |
|---|---|---|---|---|---|
| PDF Document | .pdf | 10 MB | 500 MB (up to 3000 pages) | All | Native text extraction used when available |
| Portable Network Graphics | .png | 10 MB | 10 MB | All | Best for lossless digital exports |
| JPEG Image | .jpeg, .jpg | 10 MB | 10 MB | All | Use max quality settings for best OCR results |
| TIFF Image | .tiff, .tif | 10 MB | 500 MB | All | Standard for physical scanners, multi-page supported |
| WebP Image | .webp | 10 MB | 10 MB | All | High compression ratio, fast upload |
| Microsoft Word | .docx | 10 MB | 50 MB | Extract, Classify | Automatically converted to PDF internally |
GPU Affinity Notes
FOTOhub intelligently routes document jobs to specific hardware profiles. Standard OCR and layout parsing use dense CPU clusters, whereas signature detection and checkbox analysis are routed to Vision-Language Models (VLMs) on GPU4 and GPU5 nodes.
Unit Economics & Pure USD Wallet Billing
FOTOhub routes all document intelligence requests through the prepaid USD wallet at exact 1:1 pass-through rates. There are no artificial platform credits, no monthly minimums, and no PLN conversions. If an upstream provider fails or rejects a malformed document, your wallet is automatically refunded.
| Stage | Operation / Endpoint | Provider / Engine | Unit Cost (USD) | Precision & Notes |
|---|---|---|---|---|
| Text Detection | /v1/documents/extract | AWS Textract / Tesseract | $0.005 / page | Fast OCR for plain text and raw line numbers |
| Expense Parsing | /v1/ai/document/analyze-expense | AWS Textract AnalyzeExpense | $0.010 / page | Specialized extractor for invoices, vendor headers, line items & totals |
| Table & Form Extraction | /v1/documents/analyze | AWS Textract AnalyzeDocument | $0.015 / page | Deep structural extraction of multi-column tables, forms, and key-values |
| PII Redaction | /v1/documents/redact | NLP Engine | $0.008 / page | Masking of sensitive entities (names, emails, SSN, CC) |
| Document Classification | /v1/documents/classify | VLM Zero-Shot | $0.003 / doc | Categorization routing |
| Batch Processing | /v1/documents/batch | Queue Manager | $0.004 / doc | Async scheduling fee (added to per-page costs) |
| Math Audit & Verification | /sandbox/exec-python | Firecracker MicroVM (512 MB) | $0.0005 / run | Sub-200ms air-gapped deterministic reconciliation script |
Manual Data Entry Cost Comparison Average manual data entry per invoice takes 2-3 minutes and costs approximately $1.50 - $2.50 in human labor. The FOTOhub pipeline processes the same invoice in under 2 seconds for a total cost of ~$0.0155 (Extraction + Math Audit). This represents a 99% cost reduction with mathematically guaranteed accuracy.
Balance Safety & Idempotency
Every API route validates your wallet.available_usd balance before dispatching OCR workers. If the balance cannot cover the requested page count, the API immediately halts with an explicit 402 Payment Required detailing the exact shortfall and top-up URL.
1. Document Classification API (/v1/documents/classify)
Automatically categorizes incoming documents to route them to the most efficient extraction pipeline.
Supported Document Categories
INVOICE- Commercial invoices from vendorsRECEIPT- Point of sale receipts, thermal printsPURCHASE_ORDER- B2B purchase ordersCONTRACT- Legal agreements with signaturesTAX_FORM- W-2, 1099, and international tax formsID_CARD- Driver's licenses, passports (automatically routes to redaction)BANK_STATEMENT- Financial statements with ledger tablesOTHER- Uncategorized or generic text documents
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
document_b64 | String | Yes* | None | Base64 encoded document content. |
document_url | String | Yes* | None | Publicly accessible URL or presigned S3/R2 link. |
top_k | Integer | No | 1 | Number of categories to return with confidence scores. |
* Provide either document_b64 or document_url, not both.
Implementation
import requests
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
response = requests.post(
"https://apis.fotohub.app/v1/documents/classify",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"document_url": "https://example.com/docs/vendor_doc.pdf", "top_k": 3}
)
print(json.dumps(response.json(), indent=2))import axios from 'axios';
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
async function classifyDoc() {
const { data } = await axios.post(
'https://apis.fotohub.app/v1/documents/classify',
{ document_url: "https://example.com/docs/vendor_doc.pdf", top_k: 3 },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(JSON.stringify(data, null, 2));
}
classifyDoc();package main
import (
"bytes"
"fmt"
"net/http"
"os"
"io"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
if apiKey == "" {
apiKey = "fh_live_testkey_123456789"
}
payload := []byte(`{"document_url":"https://example.com/docs/vendor_doc.pdf", "top_k": 3}`)
req, _ := http.NewRequest("POST", "https://apis.fotohub.app/v1/documents/classify", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}curl -X POST https://apis.fotohub.app/v1/documents/classify \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_url": "https://example.com/docs/vendor_doc.pdf",
"top_k": 3
}'2. Main OCR Extraction API (/v1/documents/extract)
Extracts raw text, line geometry, and paragraph bounding boxes. Best for contracts, articles, and unstructured text where layout is not highly tabular.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
document_b64 | String | Yes* | None | Base64 encoded document content. |
document_url | String | Yes* | None | Publicly accessible URL or presigned S3/R2 link. |
language | String | No | auto | Force ISO 639-1 language code (e.g., en, fr). |
include_geometry | Boolean | No | false | Return precise X/Y polygon bounding boxes. |
* Provide either document_b64 or document_url, not both.
Response Output Fields Explained
text: The full concatenated raw text of the document.pages[].lines[].text: Text bounded to a specific horizontal line in the document.pages[].lines[].geometry: The absolute polygon coordinates of the line boundary.usd_charged: The exact USD amount deducted from the wallet for this API call.wallet.available_usd: The remaining USD balance after this deduction.
Implementation
import requests
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
response = requests.post(
"https://apis.fotohub.app/v1/documents/extract",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_url": "https://example.com/docs/contract.pdf",
"include_geometry": True
}
)
print(json.dumps(response.json(), indent=2))import axios from 'axios';
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
async function extractText() {
const { data } = await axios.post(
'https://apis.fotohub.app/v1/documents/extract',
{ document_url: "https://example.com/docs/contract.pdf", include_geometry: true },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(JSON.stringify(data, null, 2));
}
extractText();package main
import (
"bytes"
"fmt"
"net/http"
"os"
"io"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
payload := []byte(`{"document_url":"https://example.com/docs/contract.pdf", "include_geometry": true}`)
req, _ := http.NewRequest("POST", "https://apis.fotohub.app/v1/documents/extract", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}curl -X POST https://apis.fotohub.app/v1/documents/extract \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_url": "https://example.com/docs/contract.pdf",
"include_geometry": true
}'3. Deep Analysis API (/v1/documents/analyze)
Extracts complex multi-column tables, key-value pairs (forms), checkbox states, and detects signatures.
Form Field Extraction & Key-Value Pairs
The Analyze API maps form layouts into strict Key-Value pairs. It identifies the "Key" (e.g., "First Name:") and pairs it with the user-entered "Value" (e.g., "Jane"). It also detects checkboxes and returns their state as SELECTED or NOT_SELECTED. Signatures are detected and bounded by geometry polygons.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
document_b64 | String | Yes* | None | Base64 encoded document content. |
document_url | String | Yes* | None | Publicly accessible URL or presigned S3/R2 link. |
features | Array | Yes | [] | List of features: ["TABLES", "FORMS", "SIGNATURES"] |
queries | Array | No | [] | Natural language questions (e.g., ["What is the patient name?"]) |
Table Detection Output JSON Example
{
"usd_charged": 0.015,
"wallet": {
"available_usd": 2450.75
},
"tables": [
{
"table_id": "table_1",
"rows": 4,
"columns": 3,
"cells": [
{
"row_index": 0,
"column_index": 0,
"text": "Item Description",
"is_header": true
},
{
"row_index": 1,
"column_index": 0,
"text": "Industrial Widget A",
"is_header": false
}
]
}
],
"forms": {
"Patient Name": "John Doe",
"Date of Birth": "1980-05-12",
"Smoker": "SELECTED"
},
"signatures": [
{
"page": 1,
"confidence": 99.8,
"geometry": {
"BoundingBox": { "Width": 0.1, "Height": 0.05, "Left": 0.2, "Top": 0.8 }
}
}
]
}Implementation
import requests
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
response = requests.post(
"https://apis.fotohub.app/v1/documents/analyze",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_url": "https://example.com/docs/medical_form.pdf",
"features": ["TABLES", "FORMS", "SIGNATURES"]
}
)
print(json.dumps(response.json(), indent=2))import axios from 'axios';
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
async function analyzeDoc() {
const { data } = await axios.post(
'https://apis.fotohub.app/v1/documents/analyze',
{ document_url: "https://example.com/docs/medical_form.pdf", features: ["TABLES", "FORMS", "SIGNATURES"] },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(JSON.stringify(data, null, 2));
}
analyzeDoc();package main
import (
"bytes"
"fmt"
"net/http"
"os"
"io"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
payload := []byte(`{"document_url":"https://example.com/docs/medical_form.pdf", "features": ["TABLES", "FORMS"]}`)
req, _ := http.NewRequest("POST", "https://apis.fotohub.app/v1/documents/analyze", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}curl -X POST https://apis.fotohub.app/v1/documents/analyze \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_url": "https://example.com/docs/medical_form.pdf",
"features": ["TABLES", "FORMS", "SIGNATURES"]
}'4. PII Redaction API (/v1/documents/redact)
Identifies and masks Personally Identifiable Information (PII) before returning the document or text. Essential for GDPR, HIPAA, and CCPA compliance.
Supported Entity Types
PERSON_NAME- First, last, and full names.EMAIL_ADDRESS- Email addresses.PHONE_NUMBER- International and local phone numbers.SSN- Social Security Numbers and national ID strings.CREDIT_CARD- Primary Account Numbers (PAN), expiry dates.MEDICAL_TERMS- PHI (Protected Health Information), ICD-10 codes, medical conditions.ADDRESS- Physical mailing addresses.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
document_b64 | String | Yes* | None | Base64 encoded document content. |
document_url | String | Yes* | None | Publicly accessible URL or presigned S3/R2 link. |
entities | Array | No | ["ALL"] | List of entities to redact (e.g., ["SSN", "CREDIT_CARD"]). |
mask_character | String | No | * | Character used to replace the text. |
return_redacted_pdf | Boolean | No | false | If true, returns a base64 encoded PDF with black bounding boxes over PII. |
Implementation
import requests
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
response = requests.post(
"https://apis.fotohub.app/v1/documents/redact",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_url": "https://example.com/docs/loan_app.pdf",
"entities": ["SSN", "PERSON_NAME", "CREDIT_CARD"],
"return_redacted_pdf": True
}
)
print(json.dumps(response.json(), indent=2))import axios from 'axios';
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
async function redactDoc() {
const { data } = await axios.post(
'https://apis.fotohub.app/v1/documents/redact',
{ document_url: "https://example.com/docs/loan_app.pdf", entities: ["SSN", "CREDIT_CARD"], return_redacted_pdf: true },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(JSON.stringify(data, null, 2));
}
redactDoc();package main
import (
"bytes"
"fmt"
"net/http"
"os"
"io"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
payload := []byte(`{"document_url":"https://example.com/docs/loan_app.pdf", "entities": ["ALL"], "return_redacted_pdf": true}`)
req, _ := http.NewRequest("POST", "https://apis.fotohub.app/v1/documents/redact", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}curl -X POST https://apis.fotohub.app/v1/documents/redact \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_url": "https://example.com/docs/loan_app.pdf",
"entities": ["SSN", "PERSON_NAME", "CREDIT_CARD"],
"return_redacted_pdf": true
}'5. Async Batch Processing API (/v1/documents/batch)
For high-volume financial workflows, legal discovery, and historical backfills, use the Batch API. It processes hundreds or thousands of documents concurrently.
S3 / R2 Output Destination (BYOB)
You can provide an output_config with an S3 bucket or Cloudflare R2 bucket. FOTOhub will write the structured JSON results directly into your bucket.
Request Parameters (POST /v1/documents/batch)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
documents | Array | Yes | None | List of objects containing document_url or document_b64. |
operation | String | Yes | None | The target operation: EXTRACT, ANALYZE, REDACT, or CLASSIFY. |
webhook_url | String | No | None | URL to POST the results or status updates upon completion. |
output_config | Object | No | None | BYOB S3/R2 configuration for output delivery. |
Webhook Delivery Pattern
When the batch is complete, FOTOhub sends a POST request to your webhook_url. The payload contains the batch_id and status. To ensure the webhook came from FOTOhub, verify the X-FotoHub-Signature header using HMAC-SHA256 and your API key.
Implementation
import asyncio
import aiohttp
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
API_BASE = "https://apis.fotohub.app"
async def process_batch(documents, operation="ANALYZE"):
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
payload = {
"documents": [{"document_url": doc} for doc in documents],
"operation": operation,
"output_config": {
"s3_bucket": "my-enterprise-results",
"s3_prefix": "batch-2026/invoices/"
}
}
async with aiohttp.ClientSession() as session:
# 1. Dispatch Batch Job
async with session.post(f"{API_BASE}/v1/documents/batch", json=payload, headers=headers) as resp:
data = await resp.json()
batch_id = data.get("batch_id")
print(f"Batch {batch_id} scheduled. Cost pending.")
# 2. Poll Status (if not using Webhooks)
while True:
await asyncio.sleep(10) # SSE streaming progress is also supported
async with session.get(f"{API_BASE}/v1/documents/batch/{batch_id}", headers=headers) as stat_resp:
status_data = await stat_resp.json()
state = status_data.get("status")
print(f"Status: {state} | Progress: {status_data.get('progress', 0)}%")
if state in ["COMPLETED", "FAILED", "PARTIAL_SUCCESS"]:
print(f"Final USD Charged: ${status_data.get('usd_charged')}")
return status_data
docs = ["https://s3.aws.com/doc1.pdf", "https://s3.aws.com/doc2.pdf"]
asyncio.run(process_batch(docs))import express from 'express';
import crypto from 'crypto';
const app = express();
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
// Use raw body parser to correctly compute HMAC
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/fotohub', (req, res) => {
const signature = req.headers['x-fotohub-signature'] as string;
const rawBody = req.body;
// Verify HMAC-SHA256 signature
const expectedSignature = crypto
.createHmac('sha256', API_KEY)
.update(rawBody)
.digest('hex');
if (signature !== expectedSignature) {
console.error("Invalid signature!");
return res.status(401).send("Unauthorized");
}
const payload = JSON.parse(rawBody.toString());
console.log(`Batch ${payload.batch_id} completed with status ${payload.status}`);
console.log(`Billed: $${payload.usd_charged} USD`);
// Process payload.results or check S3 bucket
res.status(200).send("OK");
});
app.listen(3000, () => console.log('Webhook receiver running on port 3000'));package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
apiBase := "https://apis.fotohub.app"
// Construct payload
payload := map[string]interface{}{
"operation": "ANALYZE",
"webhook_url": "https://api.mycompany.com/webhooks/fotohub",
"documents": []map[string]string{
{"document_url": "https://example.com/doc1.pdf"},
{"document_url": "https://example.com/doc2.pdf"},
},
}
body, _ := json.Marshal(payload)
// Dispatch
req, _ := http.NewRequest("POST", apiBase+"/v1/documents/batch", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println("Batch Dispatch Response:", string(respBody))
// In production, Go applications should use goroutines + channels
// to listen for webhook callbacks or concurrently poll the GET endpoint.
}# 1. Dispatch Batch Job
curl -X POST https://apis.fotohub.app/v1/documents/batch \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"operation": "ANALYZE",
"documents": [
{"document_url": "https://example.com/doc1.pdf"},
{"document_url": "https://example.com/doc2.pdf"}
],
"output_config": {
"s3_bucket": "my-enterprise-results"
}
}'
# 2. Poll Batch Status
curl -X GET https://apis.fotohub.app/v1/documents/batch/batch_123456 \
-H "Authorization: Bearer fh_live_your_api_key"6. AWS Textract Wrapper API (/v1/textract/analyze)
A direct passthrough to AWS Textract for users who have existing Textract integrations but want to utilize FOTOhub's USD prepaid billing and Firecracker math audit sandbox.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
document_b64 | String | Yes* | None | Base64 encoded document content. |
document_url | String | Yes* | None | Publicly accessible URL or presigned S3/R2 link. |
feature_types | Array | No | [] | Maps directly to Textract FeatureTypes. |
Implementation
import requests
import os
import json
API_KEY = os.environ.get("FOTOHUB_API_KEY", "fh_live_testkey_123456789")
response = requests.post(
"https://apis.fotohub.app/v1/textract/analyze",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_url": "https://example.com/docs/complex_layout.pdf",
"feature_types": ["TABLES", "FORMS", "LAYOUT"]
}
)
print(json.dumps(response.json(), indent=2))import axios from 'axios';
const API_KEY = process.env.FOTOHUB_API_KEY || "fh_live_testkey_123456789";
async function textractCall() {
const { data } = await axios.post(
'https://apis.fotohub.app/v1/textract/analyze',
{ document_url: "https://example.com/docs/complex_layout.pdf", feature_types: ["TABLES"] },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(JSON.stringify(data, null, 2));
}
textractCall();package main
import (
"bytes"
"fmt"
"net/http"
"os"
"io"
)
func main() {
apiKey := os.Getenv("FOTOHUB_API_KEY")
payload := []byte(`{"document_url":"https://example.com/docs/complex_layout.pdf", "feature_types": ["FORMS"]}`)
req, _ := http.NewRequest("POST", "https://apis.fotohub.app/v1/textract/analyze", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}curl -X POST https://apis.fotohub.app/v1/textract/analyze \
-H "Authorization: Bearer fh_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_url": "https://example.com/docs/complex_layout.pdf",
"feature_types": ["TABLES", "FORMS"]
}'7. Automated Invoice Validation Workflow
Combine the above endpoints with the Firecracker Math Sandbox to build a zero-touch AP automation flow:
- Ingest: File arrives via email parsing (converted to Base64) or S3 upload.
- Classify: Call
/v1/documents/classify. IfINVOICE, proceed. - Analyze: Call
/v1/documents/analyzeor/v1/ai/document/analyze-expense. - Transform: Map the JSON response into a strict Pydantic/Zod schema.
- Math Audit: Pass the mapped schema to
/sandbox/exec-pythonfor line-item vs subtotal/tax verification. - Accounting Software Export: On success (
verified: true), format the JSON for your ERP.
Accounting Integration Example (QuickBooks Online)
Once the JSON is verified by the sandbox, it can be seamlessly translated into a QuickBooks Vendor Bill:
{
"Line": [
{
"DetailType": "ItemBasedExpenseLineDetail",
"Amount": 1000.00,
"ItemBasedExpenseLineDetail": {
"ItemRef": {
"value": "SKU-4029"
},
"UnitPrice": 250.00,
"Qty": 4
}
}
],
"VendorRef": {
"value": "56"
}
}SAP S/4HANA (Journal Entry API)
Direct mapping into A_JournalEntryCreateRequest utilizing supplier invoice headers (CompanyCode, Supplier, DocumentReferenceID) and item lines (DebitCreditCode: 'S', AmountInTransactionCurrency).
Oracle NetSuite (REST Web Services)
Posted to /services/rest/record/v1/vendorBill with automatic sublist populating item and expense lines matched against purchase orders.
8. Error Codes, Retry Strategies & DLQ Patterns
FOTOhub APIs use standard HTTP status codes. For production systems handling high-value documents, implement Dead Letter Queues (DLQ) for failed verifications or API timeouts.
| HTTP Code | Reason | Strategy |
|---|---|---|
400 | Malformed Request / Invalid Document | Check file size, extension, or base64 integrity. Do not retry automatically. |
401 | Unauthorized | Verify Authorization header and API_KEY validity. |
402 | Payment Required | wallet.available_usd is insufficient. Pause worker queue, trigger billing alert, and resume after top-up. |
413 | Payload Too Large | File exceeds synchronous limit (10MB). Use the /v1/documents/batch API with S3 URLs instead. |
429 | Too Many Requests | Rate limit exceeded. Implement Exponential Backoff with Jitter. |
500/503 | Upstream Engine Failure | Transient text-engine error. Safe to retry with backoff. Wallet is not charged for 5xx errors. |
Dead Letter Queue (DLQ)
If a document fails the Firecracker Sandbox mathematical audit (e.g., claimed total is $100, but line items sum to $85), it must be routed to a DLQ/Human Review queue in your application. Never post mathematically invalid invoices directly to an ERP.
Complete Pipeline Implementation
# Same implementation as before but extended
# Verification script dispatched to /sandbox/exec-python
code = '''
from decimal import Decimal, ROUND_HALF_UP
def d(val):
return Decimal(str(val)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
data = input
discrepancies = []
calculated_subtotal = Decimal("0.00")
calculated_tax = Decimal("0.00")
for idx, item in enumerate(data.get("line_items", [])):
qty = Decimal(str(item.get("quantity", 0)))
unit_price = Decimal(str(item.get("unit_price", 0)))
net_claimed = d(item.get("net_amount", 0))
expected_net = d(qty * unit_price)
if abs(net_claimed - expected_net) > Decimal("0.02"):
discrepancies.append(f"Line {idx+1} ({item.get('description')}): claimed net {net_claimed} != calculated {expected_net}")
tax_rate = Decimal(str(item.get("tax_rate", 0))) / Decimal("100")
tax_claimed = d(item.get("tax_amount", 0))
expected_tax = d(expected_net * tax_rate)
if abs(tax_claimed - expected_tax) > Decimal("0.02"):
discrepancies.append(f"Line {idx+1} ({item.get('description')}): claimed tax {tax_claimed} != calculated {expected_tax}")
calculated_subtotal += expected_net
calculated_tax += expected_tax
subtotal_claimed = d(data.get("totals", {}).get("subtotal", 0))
tax_claimed = d(data.get("totals", {}).get("total_tax", 0))
total_claimed = d(data.get("totals", {}).get("total_amount", 0))
if abs(subtotal_claimed - calculated_subtotal) > Decimal("0.05"):
discrepancies.append(f"Subtotal mismatch: claimed {subtotal_claimed} != sum {calculated_subtotal}")
if abs(tax_claimed - calculated_tax) > Decimal("0.05"):
discrepancies.append(f"Tax total mismatch: claimed {tax_claimed} != sum {calculated_tax}")
calculated_grand_total = calculated_subtotal + calculated_tax
if abs(total_claimed - calculated_grand_total) > Decimal("0.05"):
discrepancies.append(f"Grand total mismatch: claimed {total_claimed} != subtotal+tax {calculated_grand_total}")
result = {
"is_valid": len(discrepancies) == 0,
"discrepancies": discrepancies,
"calculated": {
"subtotal": str(calculated_subtotal),
"total_tax": str(calculated_tax),
"grand_total": str(calculated_grand_total)
},
"reconciliation_delta": str(total_claimed - calculated_grand_total)
}
'''Production Security & Compliance Checklist
- [x] Zero Data Retention: Documents processed through
/v1/documents/*are ephemeral in RAM and discarded immediately following response serialization. - [x] Air-Gapped MicroVM Isolation: Verification scripts execute in dedicated Firecracker virtual machines devoid of network interfaces (
eth0), eliminating SSRF vectors. - [x] Deterministic Billing: Charges are levied strictly in USD from the user's balance (
wallet.available_usd) without hidden credit exchange rates. - [x] Cryptographic Webhooks: Outbound payloads are authenticated with HMAC-SHA256 signatures via the
X-FotoHub-Signatureheader. - [x] SOC2 & HIPAA Compliant: With the
REDACTendpoint and BYOB S3 configurations, you can comply with stringent compliance mandates.

