Async verification jobs

Non-blocking verification for bulk triage and slow PDFs. Same forensic engine as POST /verify — returns immediately with job_id, then poll or receive a webhook.

When to use: document batches (claims packs, utility bills), worker queues, or integrations that must not hold an HTTP connection for 10–60s. Single-file sync remains POST /verify.

Endpoints

MethodPathPurpose
POST/api/v1/jobsEnqueue one file
POST/api/v1/jobs/batchEnqueue up to 20 files (shared batch_id)
GET/api/v1/jobs/{job_id}Poll status
GET/api/v1/jobs?batch_id=…List all jobs in a batch + summary counts
GET/api/v1/jobs/{job_id}/resultFull result (same shape as /verify) when status=completed

Job lifecycle

queued → in_progress → completed | failed

Poll response example:

{
  "job_id": "JOB-A1B2C3D4E5F6",
  "status": "completed",
  "batch_id": "BATCH-…",
  "analysis_id": "ANL-…",
  "pdf_mode": "structural",
  "model": "plica_pdf/structural/v1",
  "container_hit": true,
  "container_signals_flagged": 1,
  "narrative_status": "ready",
  "narrative_explanation": "The amount on page 1 was changed from $1,250.00 to $5,750.00 after the document was created.",
  "progress": "completed",
  "links": {
    "poll": "https://api.plicaforensic.com/api/v1/jobs/JOB-…",
    "result": "https://api.plicaforensic.com/api/v1/jobs/JOB-…/result"
  }
}

Create response (PDF) also echoes pdf_mode and model immediately:

{
  "job_id": "JOB-…",
  "status": "queued",
  "poll_url": "https://api.plicaforensic.com/api/v1/jobs/JOB-…",
  "pdf_mode": "structural",
  "model": "plica_pdf/structural/v1",
  "links": { "poll": "…" }
}

Form fields (same as /verify)

image (single job) or repeated files (batch), plus optional:

Batch — cURL

curl -s -X POST "https://api.plicaforensic.com/api/v1/jobs/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "files=@statement_jan.pdf" \
  -F "files=@statement_feb.pdf" \
  -F "files=@utility_bill.pdf" \
  -F "detail_level=detailed" \
  -F "customer_id=CUST-48291" \
  -F 'additional_info={"case_ref":"CLM-48291","queue":"claims"}' \
  -F "webhook_url=https://hooks.example.com/plica/analysis"

Response:

{
  "batch_id": "BATCH-…",
  "jobs": [
    { "job_id": "JOB-…", "status": "queued", "poll_url": "…/jobs/JOB-…" },
    …
  ]
}

Batch — poll aggregate (cURL)

curl -s "https://api.plicaforensic.com/api/v1/jobs?batch_id=BATCH-XXXXXXXXXXXX" \
  -H "Authorization: Bearer YOUR_API_KEY"

Batch — Python SDK-style helper

import time
import requests

API = "https://api.plicaforensic.com/api/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

paths = ["statement_jan.pdf", "statement_feb.pdf", "utility_bill.pdf"]
multipart = [("files", (p, open(p, "rb"), "application/pdf")) for p in paths]
data = {
    "detail_level": "detailed",
    "customer_id": "CUST-48291",
    "additional_info": '{"case_ref": "CLM-48291"}',
    "webhook_url": "https://hooks.example.com/plica/analysis",
}

batch = requests.post(f"{API}/jobs/batch", headers=HEADERS, files=multipart, data=data, timeout=120)
batch.raise_for_status()
payload = batch.json()
batch_id = payload["batch_id"]

def poll_batch(batch_id: str, timeout_s: int = 600, interval_s: float = 2.0):
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API}/jobs", params={"batch_id": batch_id}, headers=HEADERS, timeout=30)
        r.raise_for_status()
        status = r.json()
        s = status["summary"]
        if s["queued"] == 0 and s["in_progress"] == 0:
            return status
        time.sleep(interval_s)
    raise TimeoutError(f"batch {batch_id} not finished in {timeout_s}s")

status = poll_batch(batch_id)
for job in status["jobs"]:
    if job["status"] != "completed":
        print(job["job_id"], job["status"], job.get("error"))
        continue
    result = requests.get(job["links"]["result"], headers=HEADERS, timeout=60).json()
    claims = result.get("forensic_view", {}).get("claim_lists", {})
    critical = len(claims.get("critical") or [])
    print(job["analysis_id"], result["verdict"], result["fraud_score"], f"critical={critical}")

Single file async (cURL)

JOB=$(curl -s -X POST "https://api.plicaforensic.com/api/v1/jobs" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "image=@passport_scan.jpg" \
  -F "detail_level=detailed" | jq -r .job_id)

until [ "$(curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.plicaforensic.com/api/v1/jobs/$JOB" | jq -r .status)" = "completed" ]; do sleep 2; done

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.plicaforensic.com/api/v1/jobs/$JOB/result" | jq '.verdict, .fraud_score'

Completion webhook

When webhook_url is set, PLICA POSTs:

{
  "event": "plica.analysis.completed",
  "version": "1",
  "job_id": "JOB-…",
  "batch_id": "BATCH-…",
  "analysis_id": "ANL-…",
  "status": "completed",
  "verdict": "suspicious",
  "fraud_score": 62,
  "links": { "poll": "…", "result": "…" }
}

Optional HMAC: set server env ANALYSIS_JOB_WEBHOOK_SECRET — verify header X-Plica-Signature (same scheme as policy webhooks).

Limits: max 20 files per batch request; same upload size limits as /verify. Jobs run in-process via background tasks — for very high volume, plan a dedicated worker queue in front of the API.