Developer API Guide

Submit documents, poll status, and retrieve forensic analysis results. Built with Python + FastAPI + OpenCV. No authentication required — open access for hackathon evaluation.

Python Python FastAPI FastAPI OpenCV OpenCV

Quick Facts

  • Base URL (prod): https://forgery.tanuh.ai/api
  • Base URL (local): http://127.0.0.1:8000
  • Authentication: None (open access)
  • Accepted: PDF, JPG, PNG, TIFF, BMP, WEBP
  • Max file size: 25 MB
  • Preview bytes cached in memory; re-fetch allowed until server restart

Endpoints

Method Path Description
POST /jobs Upload a document and create a processing job
GET /jobs/{job_id} Poll job status and progress (0.0 → 1.0)
GET /jobs/{job_id}/results Fetch complete forgery analysis results
GET /jobs/{job_id}/files/{file_name} Rendered page preview image (cached in memory, deleted later)
GET /health Service health check
Interactive docs: https://forgery.tanuh.ai/api/docs · OpenAPI JSON: https://forgery.tanuh.ai/api/openapi.json

1 — Upload a document

Send as multipart/form-data. Returns a job_id to poll.

curl -X POST "https://forgery.tanuh.ai/api/jobs" \
  -F "file=@/path/to/document.pdf" \
  -F "ocr_enabled=true"

2 — Poll job status

Poll until status is "complete" or "error".

curl "https://forgery.tanuh.ai/api/jobs/JOB_ID"

# Response:
{
  "job_id": "abc123",
  "status": "processing",
  "progress": 0.42
}

3 — Fetch results

Returns full forgery findings with per-page categories, regions, and summary text.

curl "https://forgery.tanuh.ai/api/jobs/JOB_ID/results"

4 — Preview image

Fetch rendered page image with bounding boxes. Preview bytes are cached in memory.

curl -L \
  "https://forgery.tanuh.ai/api/jobs/JOB_ID/files/page_001.png" \
  -o preview.png
Preview bytes are cleared when the server restarts.

Python (requests) — End-to-end flow

Upload a file, poll for completion, then fetch results.

# pip install requests
import time
import requests

BASE_URL = "https://forgery.tanuh.ai/api"
FILE_PATH = "/path/to/document.pdf"

with open(FILE_PATH, "rb") as handle:
    response = requests.post(
        f"{BASE_URL}/jobs",
        files={"file": handle},
        data={"ocr_enabled": "true"},
        timeout=60,
    )

response.raise_for_status()
job_id = response.json()["job_id"]

for _ in range(6):
    status = requests.get(f"{BASE_URL}/jobs/{job_id}", timeout=30).json()
    if status.get("status") in ("complete", "error"):
        break
    time.sleep(2)

results = requests.get(f"{BASE_URL}/jobs/{job_id}/results", timeout=30)
print(results.json())

Results Response Example

Job status response
{
  "job_id": "abc123",
  "status": "complete",
  "progress": 1.0,
  "created_at": "2026-05-14T10:04:12Z"
}
Full results payload
{
  "job_id": "abc123",
  "file_name": "report.pdf",
  "category_summary": {
    "C1": 2, "C3": 1
  },
  "findings_summary": {
    "summary_text": "Page 1: Added content ...",
    "findings": [{
      "page": 1,
      "category_id": "C3",
      "category_label": "Added content",
      "summary": "Page 1: Possible stamp alteration",
      "box": {"x":120,"y":80,"w":200,"h":60}
    }]
  },
  "pages": [{
    "page_number": 1,
    "image_url": "/jobs/abc123/files/page_001.png",
    "categories": ["C3"],
    "regions": [...]
  }]
}

Privacy & Data Handling

Uploaded files are deleted immediately after processing completes. Rendered preview images are cached in-memory and cleared on server restart. No document data is written to disk or stored in any database.