Mighty
Why MightyCitadel
Use cases
LendingIncome files before you fundInsuranceAppraisals to claim photosClaimsPhotos and estimates at first noticeAll use cases
SecurityDocsLog in
See it on your files

Docs

Direct SaaS purchase is no longer offered

New commercial terms are enterprise or custom, scoped on a call.

See it on your files
Browse docs
Start Here
Citadel Docs5-Minute QuickstartMighty PlatformUse CasesFAQ
Core Concepts
How Citadel WorksModalities And AttacksModes And ToleranceChoose Scan SettingsGlossarySessions And Scan GroupsDriftBilling, SCU, And Limits
Integration Guides
Integration GuidesMultimodal SupportScan Text And OCR OutputScan File UploadsDamage Photo AI Fraud ReviewScan Model OutputAsync Deep ScansError Handling
Frameworks
Framework Integration MapVercel AI SDK Chat GuardrailNext.js Upload GuardrailBackend API HelpersDocument Processing PipelineLangChain + LangGraph Guardrail
Workflows
Workflow Playbooks
API Reference
POST /v1/scan
Use With AI
Use With AI
Start Here
Citadel Docs5-Minute QuickstartMighty PlatformUse CasesFAQ
Core Concepts
How Citadel WorksModalities And AttacksModes And ToleranceChoose Scan SettingsGlossarySessions And Scan GroupsDriftBilling, SCU, And Limits
Integration Guides
Integration GuidesMultimodal SupportScan Text And OCR OutputScan File UploadsDamage Photo AI Fraud ReviewScan Model OutputAsync Deep ScansError Handling
Frameworks
Framework Integration MapVercel AI SDK Chat GuardrailNext.js Upload GuardrailBackend API HelpersDocument Processing PipelineLangChain + LangGraph Guardrail
Workflows
Workflow Playbooks
API Reference
POST /v1/scan
Use With AI
Use With AI
Loading docs…
Mighty

Citadel is Mighty's product. It finds generated or AI-edited fakes in customer files.

See it on your files

Platform

  • For Lending
  • For Insurance
  • For Claims
  • Product
  • Use Cases
  • Contact
  • Docs

Company

  • Why Mighty
  • Blog

Trust

  • Security
  • Status
  • Privacy
  • Terms
© 2026 Nine Suns Inc. All rights reserved.Citadel, from Mighty

Scan File Uploads

Scan uploaded PDFs, images, and documents before storage, OCR, AI extraction, or workflow routing.

Goal

Put a scan step between user uploads and anything that trusts the file.

Use this for claim packets, invoices, receipts, estimates, signed forms, evidence photos, identity documents, and uploaded PDFs.

Architecture

  1. Receive the upload on your server.
  2. Send the file to Citadel as multipart form data.
  3. Store the scan result with the upload record.
  4. Route the workflow based on action.
  5. Send risky uploads to review before OCR, AI extraction, or automation.

Multipart Request And Response

curl -X POST https://gateway.trymighty.ai/v1/scan \
  -H "Authorization: Bearer $MIGHTY_API_KEY" \
  -F "file=@./invoice.pdf" \
  -F "content_type=auto" \
  -F "scan_phase=input" \
  -F "mode=secure" \
  -F "focus=steg" \
  -F "profile=balanced" \
  -F "data_sensitivity=tolerant" \
  -F "metadata[source]=upload"

Use content_type=auto if your server does not know the type. Use the known type when you do.

Use focus=steg as the mixed-upload default because the first job is to catch hidden instructions, prompt injection, content steering, unsafe text, and file extraction risk before storage, OCR, or AI extraction. Use Choose Scan Settings when you need a different path.

Each entry in threats is an object with category, confidence, an optional evidence excerpt, and a human-readable reason. Switch on action; use threats[].category for audit logs.

Known Image Or PDF Evidence

Use focus=all only after you know the file is image/PDF evidence and hidden content, authenticity, and edit evidence all matter.

curl -X POST https://gateway.trymighty.ai/v1/scan \
  -H "Authorization: Bearer $MIGHTY_API_KEY" \
  -F "file=@./damage-photo.jpg" \
  -F "content_type=image" \
  -F "scan_phase=input" \
  -F "mode=secure" \
  -F "focus=all" \
  -F "profile=strict" \
  -F "data_sensitivity=tolerant"

For image/PDF authenticity-only review, use focus=ai. For localized image/PDF edit review, use focus=edits; it runs without a reference, while an optional same-modality reference_file adds pairwise corroboration. Structured Office documents do not support reference-aware edit localization. See Damage Photo AI Fraud Review.

Durable PDFs That Must Outlive The Request

For a large PDF, use the feature-gated durable upload lifecycle. The PDF goes straight to a short-lived resumable upload capability, so the gateway request does not remain open for the upload or scan lifetime:

  1. Compute the exact byte length and lowercase SHA-256 of the PDF.
  2. Create an upload with POST /v1/uploads and a stable Idempotency-Key.
  3. PUT the exact bytes to the returned upload.url. For large files, send aligned chunks with Content-Range and resume from the provider-acknowledged offset.
  4. Seal the upload with POST /v1/uploads/{upload_id}/complete.
  5. Submit upload_id to POST /v1/scan with content_type=pdf, async=true, and mode=comprehensive.
  6. On 202 Accepted, poll the Location header after Retry-After until the scan is complete or failed.

If /v1/uploads returns 404, the lifecycle is not enabled on that deployment. Treat it as unsupported; do not silently fall back to an unbounded inline PDF request.

The upload URL is a bearer capability. Keep it in memory, never log or persist it, never attach the Citadel API key to it, and reject redirects. If a browser performs the byte transfer, obtain the capability through your authenticated backend; the Citadel API key must remain server-side.

Create The Upload

The default upload limit is 200 MiB and the service has a 512 MiB hard safety ceiling. A deployment may enforce a lower limit. Byte acceptance does not bypass the account's PDF page, embedded-image, complexity, or billing limits.

curl --fail-with-body --max-redirs 0 \
  -X POST https://gateway.trymighty.ai/v1/uploads \
  -H "Authorization: Bearer $MIGHTY_API_KEY" \
  -H "Idempotency-Key: $UPLOAD_IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"size_bytes\":$PDF_SIZE_BYTES,\"sha256\":\"$PDF_SHA256\",\"content_type\":\"application/pdf\"}"

A new session returns 201. An identical replay returns 200 and replayed: true. Reusing the key with a different size, digest, or content type returns 409.

{
  "upload_id": "c4ab63eb-cd70-4de8-a414-7d131c74dd06",
  "status": "initiated",
  "replayed": false,
  "upload": {
    "url": "https://storage.googleapis.com/upload/…",
    "method": "PUT",
    "protocol": "gcs_resumable_v1",
    "recommended_chunk_bytes": 8388608,
    "chunk_alignment_bytes": 262144,
    "headers": {
      "Content-Type": "application/pdf",
      "Content-Length": "321489"
    }
  },
  "size_bytes": 321489,
  "sha256": "99b185baa0a46d7e830f95e99b2d9749712ad84c0099b467746f41578e4b8d6b",
  "content_type": "application/pdf",
  "expires_at": "2026-07-11T15:30:00Z"
}

Transfer And Seal The Bytes

For a small PDF, one final PUT remains valid. The following request intentionally has no Citadel Authorization or X-API-Key header:

curl --fail-with-body --max-redirs 0 \
  -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  -H "Content-Length: $PDF_SIZE_BYTES" \
  --data-binary "@$PDF_PATH"

For a large PDF, use 8 MiB chunks (non-final chunks must be a multiple of 256 KiB). Each request adds a per-chunk Content-Length and a Content-Range such as bytes 0-8388607/41482314. A successful non-final chunk returns 308 Resume Incomplete; its Range: bytes=0-N header is the authoritative persisted offset. Never assume every byte sent was stored.

If a chunk has a transport-uncertain outcome or receives 408, 429, or a retryable 5xx, query the same session before resending:

curl --max-redirs 0 -i \
  -X PUT "$UPLOAD_URL" \
  -H "Content-Length: 0" \
  -H "Content-Range: bytes */$PDF_SIZE_BYTES"

308 means resume at one byte after the returned Range; a missing Range means start at byte zero. 200 or 201 means the object is already complete. Retry the same session with bounded exponential backoff and jitter. Do not create parallel sessions, log the capability URL, or call complete before every byte is acknowledged. This follows the Cloud Storage resumable-upload protocol.

Then seal it:

curl --fail-with-body --max-redirs 0 \
  -X POST "https://gateway.trymighty.ai/v1/uploads/$UPLOAD_ID/complete" \
  -H "Authorization: Bearer $MIGHTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Completion verifies the tenant-bound object key, immutable provider generation, exact size, declared digest metadata, and PDF content type. Before processing, the worker streams that exact generation and recomputes its SHA-256 and %PDF- magic. A successful or replayed completion returns 200 with status: ready or status: consumed. Calling it before the provider finalizes the object returns 409; a manifest mismatch returns 422.

Enqueue And Poll

curl --fail-with-body --max-redirs 0 \
  -D - \
  -X POST https://gateway.trymighty.ai/v1/scan \
  -H "Authorization: Bearer $MIGHTY_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"upload_id\":\"$UPLOAD_ID\",\"content_type\":\"pdf\",\"async\":true,\"mode\":\"comprehensive\",\"focus\":\"all\",\"scan_phase\":\"input\",\"request_id\":\"$REQUEST_ID\"}"

202 Accepted is the only successful enqueue response. It includes Location: /v1/scan/{scan_id} and Retry-After. Retry an unchanged scan body if the response is lost; the consumed upload and request fingerprint return the original accepted scan instead of creating a duplicate. Changing scan settings after the upload is consumed returns 409; create a new upload when you intentionally need a different scan.

429 means capacity backpressure and no new job was accepted. 503 means the durable queue, store, or healthy worker dependency was unavailable. Honor Retry-After, add bounded exponential backoff with jitter, and retry idempotently. Never replace this path with an unbounded inline request.

Abort an upload you will not scan with DELETE /v1/uploads/{upload_id}. A 202 abort response means durable cleanup was scheduled; deletion may still be in progress.

Node Helper

export async function scanUpload(file: File, workflowId: string) {
  const form = new FormData();
  form.append("file", file);
  form.append("content_type", "auto");
  form.append("scan_phase", "input");
  form.append("mode", "secure");
  form.append("focus", "steg");
  form.append("data_sensitivity", "tolerant");
  form.append("session_id", workflowId);

  const response = await fetch("https://gateway.trymighty.ai/v1/scan", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MIGHTY_API_KEY}`,
    },
    body: form,
  });

  if (!response.ok) {
    throw new Error(`Citadel upload scan failed with ${response.status}`);
  }

  return response.json();
}

Routing Logic

export function routeUpload(scan: { scan_status?: string; action?: string }) {
  if (scan.scan_status === "pending") {
    return "keep_quarantined_and_poll";
  }

  if (scan.scan_status === "failed") {
    return "store_quarantined_and_queue_review";
  }

  if (scan.action === "ALLOW") {
    return "store_and_process";
  }

  if (scan.action === "REVIEW" || scan.action === "WARN") {
    return "store_quarantined_and_queue_review";
  }

  return "reject_or_quarantine";
}

Common Mistakes

  • Sending files from the browser directly to Citadel. Keep the API key on your server.
  • Running OCR first on high-risk files. Scan the file before the OCR or extraction step when possible.
  • Logging a durable upload.url or forwarding the Citadel API key to that URL.
  • Treating 202, pending, REVIEW, or failed as permission to continue.
  • Treating a WARN as a failed upload. It is often a review route.
  • Dropping scan_group_id. You need it when scanning extracted text or model output from the same file.

Production Checklist

  • Scan before permanent trust decisions.
  • Quarantine WARN and BLOCK uploads if your workflow stores them.
  • Store scan_id, scan_group_id, content_type_detected, action, and risk_score.
  • Add upload size limits before forwarding.
  • Handle 413 as a size or tier limit path.
  • Handle 402 as a billing or tier cap path.
  • Prefer async deep scan for large PDFs or high-value image evidence.
  • For durable PDFs, keep upload initialization and scan retries idempotent, honor Retry-After, and abort abandoned upload tickets.
Next step

Ready to scan real traffic?

Book a working session on your files. Starting October 1, 2026, new commercial terms are enterprise or custom.

See it on your filesHandle limits

AI-Agent Prompt

AI-ready prompt
Add file upload scanning

Paste this into Cursor, Codex, Claude Code, or Windsurf.

Add Citadel to the server-side file upload flow.

Requirements:
- Use multipart form data.
- Send the upload to POST https://gateway.trymighty.ai/v1/scan.
- Use content_type=auto unless the route knows image, pdf, or document.
- Use scan_phase=input, mode=secure, focus=steg, data_sensitivity=tolerant for mixed uploads. Use focus=all only for known image/PDF evidence that needs authenticity or edit review.
- Store the result with the upload record.
- Route ALLOW to normal storage and processing.
- Route WARN to quarantine plus human review.
- Route BLOCK to reject or quarantine.
- Preserve scan_group_id for later OCR output and model output scans.

Acceptance criteria:
- API key never reaches the browser.
- Tests cover ALLOW, WARN, BLOCK, 402, 413, and 429.
- Upload errors use safe fallback behavior.
Related docs

Keep going from here

Choose Scan SettingsPick mixed-upload settings.Scan Text And OCR OutputScan extracted text next.Error HandlingHandle limits and failures.Billing And LimitsPlan for file size and SCU.

On this page

GoalArchitectureMultipart Request And ResponseKnown Image Or PDF EvidenceDurable PDFs That Must Outlive The RequestCreate The UploadTransfer And Seal The BytesEnqueue And PollNode HelperRouting LogicCommon MistakesProduction ChecklistAI-Agent Prompt