{
  "openapi": "3.1.0",
  "info": {
    "title": "CapchaCloud Trust API",
    "version": "1.0.0",
    "description": "Server-side lead verification, trust scoring, consent evidence, defense packets, suppression, transaction evidence (any rail — not payment-specific), and public integrity verification. Write endpoints accept an 'Idempotency-Key' header for safe retries (24h replay window). Errors use { error, detail }. CapchaShield (KYC/AML compliance checks, case management, evidence packages) is a separate worker — see the 'servers' override on its operations below. Authoritative guide: https://capchacloud.com/integrate.md",
    "contact": { "name": "CapchaCloud", "url": "https://capchacloud.com/support.html" }
  },
  "servers": [{ "url": "https://capchacloud.com" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Secret API key as 'Authorization: Bearer <key>'." },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Secret API key as 'X-API-Key: <key>'." }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" }, "detail": { "type": "string" } }
      },
      "LeadInput": {
        "type": "object",
        "properties": {
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string", "description": "E.164 preferred, e.g. +15551234567" }
        }
      },
      "TrustScore": {
        "type": "object",
        "properties": {
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "email": { "type": "object" },
          "phone": { "type": "object" },
          "suppressed": { "type": "boolean" }
        }
      },
      "TxnParty": {
        "type": "object",
        "properties": {
          "role": { "type": "string" },
          "human_verified": { "type": "boolean", "nullable": true },
          "identity_assurance_level": { "type": "string", "enum": ["none", "self_asserted", "authenticated", "gov_id"] },
          "biometric_verified": { "type": "boolean", "nullable": true },
          "compliance_vault_hash": { "type": "string", "description": "A CapchaShield check's seal.vault_hash — verified server-side, never trusted blind." },
          "compliance_subject_ref": { "type": "string", "description": "Must match the referenced CapchaShield check's subject_ref exactly." }
        }
      },
      "ShieldCheckPolicy": {
        "type": "object",
        "additionalProperties": {
          "type": "object",
          "properties": { "tier": { "type": "string", "enum": ["native_screening_aid", "certified_vendor"] }, "vendor": { "type": "string" } }
        }
      }
    }
  },
  "paths": {
    "/api/v1/verify-lead": {
      "post": {
        "summary": "Verify a lead (email syntax + MX, phone E.164, suppression).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadInput" } } } },
        "responses": { "200": { "description": "Verification result" }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }
      }
    },
    "/api/v1/trust-score": {
      "post": {
        "summary": "Compute a 0–100 lead trust score (bot/risk + email + phone + suppression).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadInput" } } } },
        "responses": { "200": { "description": "Score", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrustScore" } } } }, "401": { "description": "Unauthorized" } }
      }
    },
    "/api/v1/verified-consent": {
      "post": {
        "summary": "Full Verified-Human-Consent record for a record_id (includes PII; owner only).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["record_id"], "properties": { "record_id": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Verified consent object" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/defense-packet": {
      "post": {
        "summary": "Court-ready defense memo for a record_id (HTML by default; format:'json' for data).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["record_id"], "properties": { "record_id": { "type": "string" }, "format": { "type": "string", "enum": ["html", "json"] } } } } } },
        "responses": { "200": { "description": "Defense packet" } }
      }
    },
    "/api/v1/suppression/add": {
      "post": {
        "summary": "Add an email/phone to the tenant suppression (DNC) list.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadInput" } } } },
        "responses": { "200": { "description": "Added" } }
      }
    },
    "/api/v1/suppression/check": {
      "post": {
        "summary": "Check whether an email/phone is suppressed.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadInput" } } } },
        "responses": { "200": { "description": "Suppression status" } }
      }
    },
    "/api/v1/verify-record": {
      "post": {
        "summary": "PUBLIC, no PII. Independently confirm a record's integrity + audit chain.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["record_id"], "properties": { "record_id": { "type": "string" }, "hash": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Integrity result (no personal data)" } }
      }
    },
    "/api/v1/transparency": {
      "get": {
        "summary": "PUBLIC. Live append-only audit-chain tip, validity, and external anchor.",
        "security": [],
        "responses": { "200": { "description": "Transparency report" } }
      }
    },
    "/api/v1/tenant/webhook": {
      "post": {
        "summary": "Register/update an HTTPS webhook endpoint; returns the signing secret.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "format": "uri" } } } } } },
        "responses": { "200": { "description": "Webhook registered" } }
      }
    },
    "/api/v1/webhook-test": {
      "post": {
        "summary": "Send a sample signed 'webhook.test' event to your registered endpoint.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "responses": { "202": { "description": "Test event queued" }, "400": { "description": "No webhook registered" } }
      }
    },
    "/api/v1/webhook-deliveries": {
      "get": {
        "summary": "Recent webhook delivery log for the tenant (status, http_status, latency, error).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }],
        "responses": { "200": { "description": "Delivery list" } }
      }
    },
    "/api/v1/txn/seal": {
      "post": {
        "summary": "Transaction Evidence Layer: seal a transaction's terms + parties. Rail-agnostic (any transaction type, not just payments) — 'internal'/'other' are valid rails.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["rail", "terms"],
                "properties": {
                  "rail": { "type": "string", "enum": ["stripe", "ach", "wire", "iso20022", "internal", "other"] },
                  "rail_ref": { "type": "string" },
                  "terms": { "type": "string", "description": "Exact disclosure/terms text (hashed)." },
                  "include_terms": { "type": "boolean" },
                  "amount": { "type": "object", "properties": { "currency": { "type": "string" }, "amount_minor": { "type": "integer" } } },
                  "parties": { "type": "array", "maxItems": 16, "items": { "$ref": "#/components/schemas/TxnParty" } }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Sealed and chain-anchored" },
          "207": { "description": "Sealed to vault but chain append failed (anchored:false)" },
          "400": { "description": "Bad request — includes a rejected compliance_vault_hash reference", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/v1/txn/verify": {
      "get": {
        "summary": "PUBLIC, no PII. Integrity, anchor, chain-inclusion, and rail-corroboration status for a sealed transaction.",
        "security": [],
        "parameters": [{ "name": "record_id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Verification result" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/txn/{recordId}/certificate": {
      "get": {
        "summary": "PUBLIC, no PII. Human-readable Transaction Evidence Certificate, including per-party identity_corroboration when a CapchaShield check backs it.",
        "security": [],
        "parameters": [{ "name": "recordId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Certificate" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/iso20022/seal": {
      "post": {
        "summary": "ISO 20022 Evidence Profile v2: seal a message's EXACT raw bytes (no trim/normalization/re-encoding). Content-Type application/xml|text/xml (raw bytes) or application/json ({message_xml}).",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/xml": { "schema": { "type": "string" } },
            "text/xml": { "schema": { "type": "string" } },
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["message_xml"],
                "properties": {
                  "message_xml": { "type": "string" },
                  "mode": { "type": "string", "enum": ["vaulted", "hash_only"] },
                  "policy_version": { "type": "string" },
                  "produce_block": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Sealed and chain-anchored" },
          "207": { "description": "Sealed to vault+chain but index write failed" },
          "400": { "description": "Bad request — empty body or unsupported Content-Type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized" },
          "413": { "description": "Message exceeds byte cap" }
        }
      }
    },
    "/api/v1/iso20022/seal-batch": {
      "post": {
        "summary": "Seal up to 50 ISO 20022 messages in one call. Sequential, NOT transactional — a failing item is recorded in results[] and processing continues. tsa defaults to 'skip' for batch (per-record RFC-3161 skipped; records still chain-appended + block-anchored). produce_block runs once at the end, not per item. Reuses the same seal core as /api/v1/iso20022/seal.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["messages"],
                "properties": {
                  "messages": { "type": "array", "maxItems": 50, "items": { "type": "object", "required": ["message_xml"], "properties": { "message_xml": { "type": "string" }, "policy_version": { "type": "string" } } } },
                  "mode": { "type": "string", "enum": ["vaulted", "hash_only"] },
                  "tsa": { "type": "string", "enum": ["per_record", "skip"] },
                  "produce_block": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Batch processed (see results[] for per-item outcome)" },
          "207": { "description": "Every item in the batch failed" },
          "400": { "description": "Bad request — empty/missing messages array or more than 50 messages", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/v1/iso20022/records": {
      "get": {
        "summary": "List sealed ISO 20022 records for the authenticated tenant. Keyset-paginated (created_at DESC, id DESC) via opaque cursor — never OFFSET. corroboration_count via a single LEFT JOIN + GROUP BY.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } },
          { "name": "uetr", "in": "query", "schema": { "type": "string" } },
          { "name": "msg_def_idr", "in": "query", "schema": { "type": "string" } },
          { "name": "mode", "in": "query", "schema": { "type": "string", "enum": ["vaulted", "hash_only"] } },
          { "name": "corroborated", "in": "query", "schema": { "type": "string", "enum": ["1", "0"] } },
          { "name": "from_ms", "in": "query", "schema": { "type": "integer" } },
          { "name": "to_ms", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Page of records + next_cursor (null when no further pages)" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/v1/iso20022/verify": {
      "get": {
        "summary": "PUBLIC, no PII. Integrity, anchor, chain-inclusion, and bank-attested corroboration status for a sealed ISO 20022 record.",
        "security": [],
        "parameters": [{ "name": "record_id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Verification result" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/iso20022/{recordId}/certificate": {
      "get": {
        "summary": "PUBLIC, no PII. Machine-readable ISO 20022 Message Evidence Certificate (JSON). Corroboration provenance is always 'bank_submitted' — no network signature is verified by CapchaCloud.",
        "security": [],
        "parameters": [{ "name": "recordId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Certificate" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/iso20022/corroborate": {
      "post": {
        "summary": "Submit a bank-received pacs.002/pacs.004 to corroborate an already-sealed record (matched via OrgnlUETR, falls back to UETR). provenance is always 'bank_submitted'.",
        "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/xml": { "schema": { "type": "string" } },
            "text/xml": { "schema": { "type": "string" } },
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["message_xml"],
                "properties": { "message_xml": { "type": "string" }, "mode": { "type": "string", "enum": ["vaulted", "hash_only"] } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Corroboration sealed and linked" },
          "207": { "description": "Corroboration sealed to vault+chain but index write failed" },
          "400": { "description": "Bad request — could not extract OrgnlUETR/UETR", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized" },
          "404": { "description": "no_matching_record" }
        }
      }
    },
    "/iso20022/certificate/{recordId}": {
      "get": {
        "summary": "PUBLIC. Printable, self-contained HTML ISO 20022 Message Evidence Certificate (inline CSS, no external assets/JS, all dynamic values HTML-escaped). Equal-weight 'what this proves' / 'what this does NOT prove' sections.",
        "security": [],
        "parameters": [{ "name": "recordId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Printable HTML certificate page" },
          "404": { "description": "Record not found or invalid id (HTML 404 page)" },
          "429": { "description": "Rate limited (HTML page)" }
        }
      }
    },
    "/api/v1/capabilities": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "get": {
        "summary": "CapchaShield: list every check adapter (native + certified-vendor), its live configured status, and the standing disclaimer. No auth.",
        "security": [],
        "responses": { "200": { "description": "Capabilities" } }
      }
    },
    "/api/v1/checks/run": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "post": {
        "summary": "CapchaShield: run one compliance check (native or a specific certified vendor) and seal the result. No silent downgrade if the requested tier isn't configured.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["check_type", "subject_ref"],
                "properties": {
                  "check_type": { "type": "string", "enum": ["id_document", "video_presence", "face_match", "sanctions_screen", "pep_adverse_media", "kyb_registration", "kyb_ubo", "device_intelligence", "transaction_monitor"] },
                  "subject_ref": { "type": "string" },
                  "payload": { "type": "object", "additionalProperties": true },
                  "policy": { "$ref": "#/components/schemas/ShieldCheckPolicy" },
                  "case_id": { "type": "string", "description": "Optional — tags this check as part of a case's evidence trail." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Sealed and chain-anchored" },
          "207": { "description": "Sealed but chain append failed (anchored:false)" },
          "400": { "description": "Bad request" },
          "401": { "description": "Unauthorized" },
          "409": { "description": "Requested tier/vendor is not configured — no silent downgrade to native" }
        }
      }
    },
    "/api/v1/evidence-package": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "get": {
        "summary": "CapchaShield: one-click evidence ZIP — every sealed check for a subject_ref, or every event in one case_id, with real images/video and a SHA-256 manifest.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "subject_ref", "in": "query", "schema": { "type": "string" } },
          { "name": "case_id", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "ZIP file (application/zip)" }, "400": { "description": "subject_ref or case_id required" }, "404": { "description": "No sealed checks found" } }
      }
    },
    "/api/v1/cases": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "post": {
        "summary": "CapchaShield CLM: open a case for a subject.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["subject_ref"], "properties": { "subject_ref": { "type": "string" }, "assigned_to": { "type": "string" } } } } } },
        "responses": { "201": { "description": "Case opened" }, "400": { "description": "Bad request" }, "401": { "description": "Unauthorized" } }
      },
      "get": {
        "summary": "CapchaShield CLM: list this tenant's cases.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["open", "in_review", "escalated", "closed"] } },
          { "name": "limit", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "Case list" } }
      }
    },
    "/api/v1/cases/{caseId}": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "get": {
        "summary": "CapchaShield CLM: get a case plus every sealed event in it, in order.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Case detail" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/cases/{caseId}/notes": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "post": {
        "summary": "CapchaShield CLM: add a comment or escalation note to an open case; optionally move status to in_review/escalated.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["author"], "properties": { "author": { "type": "string" }, "body": { "type": "string" }, "action": { "type": "string", "enum": ["comment", "escalation"] }, "set_status": { "type": "string", "enum": ["in_review", "escalated"] } } } } } },
        "responses": { "200": { "description": "Note sealed" }, "404": { "description": "Case not found" }, "409": { "description": "Case is closed (terminal)" } }
      }
    },
    "/api/v1/cases/{caseId}/close": {
      "servers": [{ "url": "https://capcha-shield.adamfrankwoodward.workers.dev", "description": "CapchaShield (separate worker)" }],
      "post": {
        "summary": "CapchaShield CLM: close a case (terminal). Requires a free-text disposition — CapchaShield never enumerates or interprets what 'approved'/'rejected' means.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["author", "disposition"], "properties": { "author": { "type": "string" }, "disposition": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Case closed" }, "404": { "description": "Case not found" }, "409": { "description": "Already closed" } }
      }
    }
  }
}
