Skip to content

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

mermaid
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.

FormatExtensionsMax File Size (Sync)Max File Size (Batch)Supported EndpointsNotes
PDF Document.pdf10 MB500 MB (up to 3000 pages)AllNative text extraction used when available
Portable Network Graphics.png10 MB10 MBAllBest for lossless digital exports
JPEG Image.jpeg, .jpg10 MB10 MBAllUse max quality settings for best OCR results
TIFF Image.tiff, .tif10 MB500 MBAllStandard for physical scanners, multi-page supported
WebP Image.webp10 MB10 MBAllHigh compression ratio, fast upload
Microsoft Word.docx10 MB50 MBExtract, ClassifyAutomatically 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.

StageOperation / EndpointProvider / EngineUnit Cost (USD)Precision & Notes
Text Detection/v1/documents/extractAWS Textract / Tesseract$0.005 / pageFast OCR for plain text and raw line numbers
Expense Parsing/v1/ai/document/analyze-expenseAWS Textract AnalyzeExpense$0.010 / pageSpecialized extractor for invoices, vendor headers, line items & totals
Table & Form Extraction/v1/documents/analyzeAWS Textract AnalyzeDocument$0.015 / pageDeep structural extraction of multi-column tables, forms, and key-values
PII Redaction/v1/documents/redactNLP Engine$0.008 / pageMasking of sensitive entities (names, emails, SSN, CC)
Document Classification/v1/documents/classifyVLM Zero-Shot$0.003 / docCategorization routing
Batch Processing/v1/documents/batchQueue Manager$0.004 / docAsync scheduling fee (added to per-page costs)
Math Audit & Verification/sandbox/exec-pythonFirecracker MicroVM (512 MB)$0.0005 / runSub-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 vendors
  • RECEIPT - Point of sale receipts, thermal prints
  • PURCHASE_ORDER - B2B purchase orders
  • CONTRACT - Legal agreements with signatures
  • TAX_FORM - W-2, 1099, and international tax forms
  • ID_CARD - Driver's licenses, passports (automatically routes to redaction)
  • BANK_STATEMENT - Financial statements with ledger tables
  • OTHER - Uncategorized or generic text documents

Request Parameters

ParameterTypeRequiredDefaultDescription
document_b64StringYes*NoneBase64 encoded document content.
document_urlStringYes*NonePublicly accessible URL or presigned S3/R2 link.
top_kIntegerNo1Number of categories to return with confidence scores.

* Provide either document_b64 or document_url, not both.

Implementation

python
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))
typescript
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();
go
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))
}
bash
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

ParameterTypeRequiredDefaultDescription
document_b64StringYes*NoneBase64 encoded document content.
document_urlStringYes*NonePublicly accessible URL or presigned S3/R2 link.
languageStringNoautoForce ISO 639-1 language code (e.g., en, fr).
include_geometryBooleanNofalseReturn 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

python
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))
typescript
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();
go
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))
}
bash
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

ParameterTypeRequiredDefaultDescription
document_b64StringYes*NoneBase64 encoded document content.
document_urlStringYes*NonePublicly accessible URL or presigned S3/R2 link.
featuresArrayYes[]List of features: ["TABLES", "FORMS", "SIGNATURES"]
queriesArrayNo[]Natural language questions (e.g., ["What is the patient name?"])

Table Detection Output JSON Example

json
{
  "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

python
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))
typescript
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();
go
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))
}
bash
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

ParameterTypeRequiredDefaultDescription
document_b64StringYes*NoneBase64 encoded document content.
document_urlStringYes*NonePublicly accessible URL or presigned S3/R2 link.
entitiesArrayNo["ALL"]List of entities to redact (e.g., ["SSN", "CREDIT_CARD"]).
mask_characterStringNo*Character used to replace the text.
return_redacted_pdfBooleanNofalseIf true, returns a base64 encoded PDF with black bounding boxes over PII.

Implementation

python
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))
typescript
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();
go
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))
}
bash
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)

ParameterTypeRequiredDefaultDescription
documentsArrayYesNoneList of objects containing document_url or document_b64.
operationStringYesNoneThe target operation: EXTRACT, ANALYZE, REDACT, or CLASSIFY.
webhook_urlStringNoNoneURL to POST the results or status updates upon completion.
output_configObjectNoNoneBYOB 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

python
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))
typescript
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'));
go
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.
}
bash
# 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

ParameterTypeRequiredDefaultDescription
document_b64StringYes*NoneBase64 encoded document content.
document_urlStringYes*NonePublicly accessible URL or presigned S3/R2 link.
feature_typesArrayNo[]Maps directly to Textract FeatureTypes.

Implementation

python
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))
typescript
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();
go
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))
}
bash
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:

  1. Ingest: File arrives via email parsing (converted to Base64) or S3 upload.
  2. Classify: Call /v1/documents/classify. If INVOICE, proceed.
  3. Analyze: Call /v1/documents/analyze or /v1/ai/document/analyze-expense.
  4. Transform: Map the JSON response into a strict Pydantic/Zod schema.
  5. Math Audit: Pass the mapped schema to /sandbox/exec-python for line-item vs subtotal/tax verification.
  6. 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:

json
{
  "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 CodeReasonStrategy
400Malformed Request / Invalid DocumentCheck file size, extension, or base64 integrity. Do not retry automatically.
401UnauthorizedVerify Authorization header and API_KEY validity.
402Payment Requiredwallet.available_usd is insufficient. Pause worker queue, trigger billing alert, and resume after top-up.
413Payload Too LargeFile exceeds synchronous limit (10MB). Use the /v1/documents/batch API with S3 URLs instead.
429Too Many RequestsRate limit exceeded. Implement Exponential Backoff with Jitter.
500/503Upstream Engine FailureTransient 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

python
# 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-Signature header.
  • [x] SOC2 & HIPAA Compliant: With the REDACT endpoint and BYOB S3 configurations, you can comply with stringent compliance mandates.