{
  "openapi": "3.1.0",
  "info": {
    "title": "Vilkax Protect API",
    "version": "1.2.0",
    "summary": "Score and explain any risk decision in one HTTPS call.",
    "description": "The Vilkax Protect API exposes the same behavioural risk engine that protects people, as a metered, scoped, fail-closed partner API. Authenticate every request with `Authorization: Bearer vlx_live_<key>`. Each endpoint requires a scope; deep fused signals bill at a higher unit multiple. Responses are explainable (reasons, evidence, confidence, a stable id) and degrade gracefully (a missing source returns a well-formed `degraded` response, never a false all-clear). This document is hand-maintained to match the live routes. EU-built.\n\n**Safe retries.** Every metered POST accepts an `Idempotency-Key` header: a repeat of the same request returns the first response verbatim instead of producing a second decision, a second review case, a second webhook and a second billed call. Records live 24 hours.\n\n**Credential lifetime.** Keys may carry an expiry (`403 key_expired`, distinct from `key_revoked`). `POST /api/partner/v1/keys/rotate` performs an overlapping rotation: both keys work for a bounded grace window, then the old one lapses on a known date.\n\n**Signed callbacks.** Outbound events carry `x-vilkax-signature: t=<unix-seconds>,v1=<hex HMAC-SHA256>` over `` `${t}.${rawBody}` ``. The timestamp is inside the HMAC, so it cannot be altered to defeat your replay window.\n\n**Sandbox.** Ask for a sandbox key (`environment: \"sandbox\"`, prefix `vlx_test_`). Every partner endpoint answers a sandbox key with a deterministic fixture (`sandbox: true`, header `x-vilkax-sandbox: 1`): no live data, no writes, never billed. Steer verdicts with magic values: a string containing `sandbox-block` or ending in `dead` gives `block`; `sandbox-review` or ending in `beef` gives `review`; anything else gives `allow`. A person-type subject is clamped to `review`, as in production. A sandbox key never authenticates against live data.",
    "contact": {
      "name": "Vilkax",
      "url": "https://vilkax.com/business/#biz-contact"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://vilkax.com",
      "description": "Production (EU edge)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Decisioning",
      "description": "Fused, explainable risk decisions."
    },
    {
      "name": "Signals",
      "description": "Single-signal lookups."
    },
    {
      "name": "KYT",
      "description": "Know-Your-Transaction screening."
    },
    {
      "name": "Account",
      "description": "Usage and quota introspection."
    },
    {
      "name": "Webhooks",
      "description": "Signed outbound events: subscriptions, the delivery log, and replay."
    },
    {
      "name": "Keys",
      "description": "API-key lifecycle: issue, list, rotate with a grace window, revoke."
    },
    {
      "name": "Audit",
      "description": "The tenant's hash-chained audit trail, and its export."
    },
    {
      "name": "Protection",
      "description": "Staff phishing reports, triage verdicts and the monthly executive report (owner/admin session)."
    },
    {
      "name": "NEXUS",
      "description": "Investigation and scam-intelligence API (operator session or a partner key scoped to its own tenant). Errors use the `{ ok:false, error, reason? }` envelope."
    }
  ],
  "paths": {
    "/api/partner/v1/decide": {
      "post": {
        "tags": [
          "Decisioning"
        ],
        "summary": "Decide on a signal bundle",
        "description": "Send taxonomy signal ids with a 0..1 strength (plus an optional text indicator and/or OSINT hint) and get back one explained decision: threat type, severity, confidence, the action to take, clarifying questions and the plain `why`. Deterministic — the same bundle always yields the same decision. Bills 3 units (a deep fused signal). PII-safe: optional `text` is redacted + classified server-side and never stored or echoed.",
        "operationId": "decide",
        "security": [
          {
            "bearerAuth": [
              "decide"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecideRequest"
              },
              "example": {
                "signals": [
                  {
                    "id": "process.remote_access_tool",
                    "value": 0.9
                  },
                  {
                    "id": "text.urgency_pressure",
                    "value": 0.8
                  },
                  {
                    "id": "transfer.outbound_initiated",
                    "value": 1
                  }
                ],
                "context": {
                  "action_kind": "transfer"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An explained decision.",
            "headers": {
              "x-billed-units": {
                "$ref": "#/components/headers/BilledUnits"
              },
              "x-response-time": {
                "$ref": "#/components/headers/ResponseTime"
              },
              "idempotent-replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "x-quota-remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecideResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/business/v1/risk/decide": {
      "post": {
        "tags": [
          "Decisioning"
        ],
        "summary": "B2B fraud decision from a feature vector",
        "description": "The B2B fintech fraud/risk decisioning engine. Submit a PII-free feature vector (`features.ts` ids with a 0..1 strength) for a transaction / account / onboarding / merchant / entity, and get one explainable decision: B2B threat type, risk tier, confidence, reasons, alternative explanations, recommended action and the human-review safeguard. Channels are gated by your tier; gated-out features are reported (transparent upsell). Bills 3 units. Rejects any feature vector that smuggles raw PII (400 `pii_in_features`).",
        "operationId": "businessRiskDecide",
        "security": [
          {
            "bearerAuth": [
              "risk:decide"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BusinessDecideRequest"
              },
              "example": {
                "features": [
                  {
                    "id": "txn.velocity_spike",
                    "value": 0.7
                  },
                  {
                    "id": "txn.new_beneficiary_high_value",
                    "value": 0.9
                  }
                ],
                "subject_kind": "transaction",
                "subject_ref": "txn_8f3a"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An explained B2B risk decision.",
            "headers": {
              "x-billed-units": {
                "$ref": "#/components/headers/BilledUnits"
              },
              "x-response-time": {
                "$ref": "#/components/headers/ResponseTime"
              },
              "idempotent-replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "x-quota-remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusinessDecideResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/api/partner/v1/signals/lookup": {
      "get": {
        "tags": [
          "Signals"
        ],
        "summary": "Look up a single signal",
        "description": "Wraps Vilkax's OSINT / URL-reputation / entity intel as a metered lookup. `type=email|url` bill 1 unit; `type=entity` (deeper sanctions fan-out) bills 3. Degrades gracefully: when no provider is configured the response is well-formed with `degraded: true` and an honest note, never a false clear.",
        "operationId": "signalsLookup",
        "security": [
          {
            "bearerAuth": [
              "signals:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email",
                "url",
                "entity"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The value to look up (max 512 chars).",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A scored signal.",
            "headers": {
              "x-billed-units": {
                "$ref": "#/components/headers/BilledUnits"
              },
              "x-response-time": {
                "$ref": "#/components/headers/ResponseTime"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignalLookupResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/partner/v1/signals/report": {
      "post": {
        "tags": [
          "Signals"
        ],
        "summary": "Report fraud you have confirmed or suspect",
        "description": "Signals IN: a bank, fintech or exchange tells Vilkax about fraud it has confirmed or suspects, and the indicators join the same threat graph consumer reports feed. Raw values are hashed on arrival and never stored; or send value_hash = SHA-256 hex of \"<kind>:<canonical>\" (canonical = trimmed, lowercased; IBAN without spaces; phone digits with an optional leading +; domain without www.; URL without scheme or trailing slash). confirmed_fraud and suspected_fraud are kept apart, never pooled. Provenance is origin partner, verification self_reported, reporter = a salted hash of the partner id; campaigns built from these reports stay candidate until a Vilkax operator confirms them, and nothing acts on any person. High-stakes categories (CSE, grooming, trafficking, abuse) are not reportable here. The same partner, reference and indicator is one report however often it is sent. Send X-Vilkax-Sandbox: 1 to validate and see the hashes without writing anything. If the graph store is unavailable the call answers 503, never a false accept. Not metered.",
        "operationId": "signalsReport",
        "security": [
          {
            "bearerAuth": [
              "signals:report"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "X-Vilkax-Sandbox",
            "in": "header",
            "required": false,
            "description": "Send 1 to validate and hash the report without writing it.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "outcome",
                  "indicators"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "confirmed_fraud",
                      "suspected_fraud"
                    ]
                  },
                  "family": {
                    "type": "string",
                    "enum": [
                      "app_fraud",
                      "investment",
                      "romance",
                      "impersonation",
                      "mule",
                      "phishing",
                      "crypto_drain",
                      "account_takeover",
                      "other"
                    ]
                  },
                  "reference": {
                    "type": "string",
                    "description": "Your own case id; hashed into the report id, so a resend is the same report."
                  },
                  "indicators": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "required": [
                        "kind"
                      ],
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "wallet",
                            "iban",
                            "email",
                            "phone",
                            "domain",
                            "url"
                          ]
                        },
                        "value": {
                          "type": "string",
                          "description": "Raw value; hashed on arrival, never stored."
                        },
                        "value_hash": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "SHA-256 hex of \"<kind>:<canonical>\"."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "outcome": "confirmed_fraud",
                "family": "mule",
                "reference": "CASE-2026-0412",
                "indicators": [
                  {
                    "kind": "iban",
                    "value": "GB29NWBK60161331926819"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted (or validated, in sandbox).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "sandbox": {
                      "type": "boolean"
                    },
                    "accepted": {
                      "type": "integer"
                    },
                    "rejected": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "report_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "hashed": {
                      "type": "array",
                      "description": "Sandbox only.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "type": "string"
                          },
                          "value_hash": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/intel/v1/risk/lookup": {
      "get": {
        "tags": [
          "Signals"
        ],
        "summary": "Scammer Reputation Index lookup",
        "description": "The SRI: 'is this entity a known scammer?'. The submitted value is hashed immediately and never stored in the clear. Returns a score, tier, confidence, breakdown and evidence — a behaviour/exposure assessment, never an accusation. Tier-gated to Pro+ (a Starter key is told to upgrade). Bills 1 unit.",
        "operationId": "sriLookup",
        "security": [
          {
            "bearerAuth": [
              "risk:lookup"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email",
                "phone",
                "domain",
                "ip",
                "wallet"
              ]
            }
          },
          {
            "name": "value",
            "in": "query",
            "required": true,
            "description": "The entity value (max 256 chars). Hashed server-side.",
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An SRI assessment.",
            "headers": {
              "x-billed-units": {
                "$ref": "#/components/headers/BilledUnits"
              },
              "x-response-time": {
                "$ref": "#/components/headers/ResponseTime"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SriResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/nexus/v1/kyt/screen": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Screen a wallet / transaction / counterparty",
        "description": "Know-Your-Transaction. Supply exactly one target: a `{ chain, address }`, a `{ chain, tx }`, or a `{ counterparty, counterparty_type }`. Composes exposure-by-category, the SRI, sanctions/PEP and a fund-trace summary into one explainable verdict (allow / review / block) with evidence and coverage. Coverage-honest: a clean lookup on limited coverage degrades to `review`, never a false `allow`. A person can never be auto-blocked (clamped to at most `review`). Tier-gated to Pro+. Bills 3 units.",
        "operationId": "kytScreen",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KytScreenRequest"
              },
              "example": {
                "chain": "eth",
                "address": "0xabc0000000000000000000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A KYT screening verdict.",
            "headers": {
              "x-billed-units": {
                "$ref": "#/components/headers/BilledUnits"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KytScreenResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/partner/v1/usage": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Current-month usage and quota",
        "description": "Returns the current calendar-month metered-unit consumption, the tier quota, the burst rate limit and a per-endpoint breakdown. This call is itself NOT metered (introspection).",
        "operationId": "usage",
        "security": [
          {
            "bearerAuth": [
              "usage:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Usage and quota.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/business/webhooks/endpoints": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List your signed webhook subscriptions",
        "description": "Returns the org's endpoints WITHOUT their secrets, plus the exact signature bytes to verify. Authenticated by a signed business-suite session resolving to owner or admin (the business_id is server-derived; there is no parameter by which a caller can widen scope). This is a CONSOLE endpoint, not part of the machine-to-machine key surface.",
        "operationId": "listWebhookEndpoints",
        "responses": {
          "200": {
            "description": "The org's endpoints and the signature contract.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    },
                    "available_events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "max_endpoints": {
                      "type": "integer"
                    },
                    "signature": {
                      "type": "object",
                      "description": "How to verify a delivery.",
                      "properties": {
                        "header": {
                          "type": "string",
                          "example": "x-vilkax-signature"
                        },
                        "format": {
                          "type": "string",
                          "example": "t=<unix-seconds>,v1=<hex HMAC-SHA256>"
                        },
                        "signed_message": {
                          "type": "string",
                          "example": "`${t}.${rawRequestBody}`"
                        },
                        "algorithm": {
                          "type": "string",
                          "example": "HMAC-SHA256"
                        },
                        "note": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not an owner/admin of a business account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create, update, rotate, test or delete an endpoint",
        "description": "One endpoint, dispatched on `action`.\n\n- `create` — register an endpoint; the signing secret is returned ONCE.\n- `update` — change `url`, `events` or `enabled`.\n- `rotate` — issue a new secret. Takes effect IMMEDIATELY (no overlap): deploy the new secret to your receiver first, or verify against both during the changeover.\n- `test`  — send a real signed `webhook.test` through the production path.\n- `delete` — remove the subscription. Delivery history is retained.\n\nThe URL must be HTTPS on a public host; loopback, private and link-local targets are refused at registration AND re-checked at delivery time (DNS can be re-pointed afterwards). Authenticated by a signed business-suite session resolving to owner or admin (the business_id is server-derived; there is no parameter by which a caller can widen scope). This is a CONSOLE endpoint, not part of the machine-to-machine key surface.",
        "operationId": "manageWebhookEndpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "create",
                      "update",
                      "rotate",
                      "test",
                      "delete"
                    ]
                  },
                  "id": {
                    "type": "string",
                    "description": "Required for every action except `create`."
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "string",
                    "example": "decision.review"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "action": "create",
                "url": "https://hooks.example-bank.com/vilkax",
                "events": "decision.review"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The action's result. `create` and `rotate` include the secret exactly once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreated"
                }
              }
            }
          },
          "400": {
            "description": "`https_required`, `private_host_not_allowed`, `invalid_url`, `unknown_event:<id>`, `bad_action`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an owner/admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in YOUR org (a foreign id is indistinguishable from a missing one).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`endpoint_limit_reached`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/business/webhooks/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delivery log",
        "description": "Every delivery attempt we made for your org: event, destination, HTTP status, attempts, latency and the error when it failed. Filter with `status` and `endpoint_id`; page with `page` + `page_size` (`limit` is accepted as an alias). Authenticated by a signed business-suite session resolving to owner or admin (the business_id is server-derived; there is no parameter by which a caller can widen scope). This is a CONSOLE endpoint, not part of the machine-to-machine key surface.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "failed"
              ]
            }
          },
          {
            "name": "endpoint_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of delivery attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "page_size": {
                      "type": "integer"
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Replay a delivery",
        "description": "Re-sends to the endpoint's CURRENT url and secret as a NEW attempt with its own audit row. It does NOT re-run the decision, and it does NOT reproduce the original body byte-for-byte — the delivery log is metadata-only by design, so a replay carries a `webhook.replay` envelope naming the original event and delivery id. A replay against a deleted or disabled endpoint is a typed 409, never a guess at a destination. Authenticated by a signed business-suite session resolving to owner or admin (the business_id is server-derived; there is no parameter by which a caller can widen scope). This is a CONSOLE endpoint, not part of the machine-to-machine key surface.",
        "operationId": "replayWebhookDelivery",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "delivery_id"
                ],
                "properties": {
                  "delivery_id": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "delivery_id": "4821"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replay outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "replayed": {
                      "type": "boolean"
                    },
                    "delivery_id": {
                      "type": "string"
                    },
                    "http_status": {
                      "type": "integer",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "integer"
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such delivery in YOUR org.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`endpoint_gone` or `endpoint_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/partner/v1/keys/rotate": {
      "post": {
        "tags": [
          "Keys"
        ],
        "summary": "Rotate an API key with a grace window",
        "description": "Mints a successor key with the SAME partner, tier and scopes, and stamps the OLD key with an expiry at the end of a grace window (default 7 days, 0-90). BOTH keys authenticate during the window; at the deadline the old key returns `403 key_expired` — a dated cutover rather than an open-ended one. `grace_days: 0` expires it immediately, for a compromise response.\n\nRotation does NOT revoke the old key: revoked and expired are distinct states, and the difference is diagnostic for whoever is paged. The new plaintext key is returned exactly once.\n\nOperator-authenticated (Cloudflare Access / founder session), not partner-key authenticated.",
        "operationId": "rotatePartnerKey",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key_id"
                ],
                "properties": {
                  "key_id": {
                    "type": "string",
                    "description": "The key to rotate away from."
                  },
                  "grace_days": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 90,
                    "default": 7
                  }
                }
              },
              "example": {
                "key_id": "pk_abc123",
                "grace_days": 7
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The successor key. `api_key` is shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "rotated": {
                      "type": "boolean"
                    },
                    "previous_key_id": {
                      "type": "string"
                    },
                    "previous_key_expires_at": {
                      "type": "string",
                      "nullable": true
                    },
                    "grace_days": {
                      "type": "integer"
                    },
                    "key_id": {
                      "type": "string"
                    },
                    "key_prefix": {
                      "type": "string"
                    },
                    "api_key": {
                      "type": "string",
                      "description": "Plaintext, shown ONCE."
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "tier": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_key_id` or `invalid_grace_days` (refused, never clamped).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`key_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`key_not_active` — a revoked key cannot mint a live successor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/business/audit/export": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "Export your audit trail (SIEM ingest)",
        "description": "Bulk, incremental export of the org's hash-chained audit trail, in the shape a SIEM ingests.\n\n- `format=ndjson` (default) — one JSON object per line; the universal SIEM shape.\n- `format=csv` — for a compliance reviewer or a static evidence artefact. Values that would otherwise be read as a spreadsheet formula are neutralised.\n- `format=json` — one object with an array.\n\n**Incremental pull.** Pass `since_id` and read `X-Vilkax-Max-Id` off the response; store it and pass it back next run. Rows come oldest-first, so an interrupted ingest leaves a resumable prefix rather than a gap. `X-Vilkax-Has-More: true` means call again immediately.\n\n**Integrity.** Every row carries `prev_hash` and `row_hash` from the append-only chain, so a modified row you hold is detectable. The chain spans all tenants, so a single-tenant slice is not contiguous — ask us for a chain attestation covering a period if you need one.\n\n**Excluded by design:** `ip_address`, `user_agent`, `request_id`, `payload_encrypted`. Those are held as processor and are not shipped into a customer-controlled system.\n\nA read failure returns 503, never an empty export — an archived empty file would become evidence that nothing happened. Owner/admin session; available to a lapsed org, which keeps the right to its own compliance evidence.",
        "operationId": "exportBusinessAudit",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ndjson",
                "csv",
                "json"
              ],
              "default": "ndjson"
            }
          },
          {
            "name": "since_id",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Return only events with a higher id."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5000,
              "default": 1000
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 lower bound on occurred_at."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 upper bound on occurred_at."
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "b2b"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export, in the requested format.",
            "headers": {
              "x-vilkax-max-id": {
                "schema": {
                  "type": "integer"
                },
                "description": "Cursor to pass back as `since_id`. On an empty page this echoes your `since_id` rather than resetting to 0."
              },
              "x-vilkax-has-more": {
                "schema": {
                  "type": "boolean"
                },
                "description": "More rows are waiting — call again."
              },
              "x-vilkax-row-count": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "type": "string"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "business_id": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    },
                    "count": {
                      "type": "integer"
                    },
                    "max_id": {
                      "type": "integer"
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "excluded_fields": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "integrity": {
                      "type": "object",
                      "properties": {
                        "scheme": {
                          "type": "string"
                        },
                        "note": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_format`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an owner/admin of a business account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`export_unavailable` — the store could not be read. Deliberately NOT an empty export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/business/protection/phish-reports": {
      "get": {
        "tags": [
          "Protection"
        ],
        "summary": "List your own phishing reports (with verdicts), or the triage queue",
        "operationId": "listPhishReports",
        "description": "Default: the caller's own reports and their verdicts. `view=queue` (owner/admin): grouped by message with a reporter COUNT only, never who reported. A missing schema is 503, never an empty list.",
        "parameters": [
          {
            "name": "view",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "queue"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "phishing",
                "simulation",
                "spam",
                "legitimate",
                "inconclusive",
                "all"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reports or queue."
          },
          "401": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "403": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "503": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          }
        }
      },
      "post": {
        "tags": [
          "Protection"
        ],
        "summary": "Report a suspected phishing message",
        "operationId": "createPhishReport",
        "description": "Enums and hostnames only; the RFC Message-ID is tenant-salted and hashed before storage. `colleagues_reported` is null below two other reporters.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "channel",
                  "reason"
                ],
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "email",
                      "sms",
                      "chat",
                      "call",
                      "web"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "suspicious_link",
                      "credential_request",
                      "payment_or_bank_change",
                      "executive_impersonation",
                      "supplier_impersonation",
                      "attachment",
                      "gift_cards",
                      "mfa_or_code_request",
                      "other"
                    ]
                  },
                  "message_id": {
                    "type": "string",
                    "maxLength": 998
                  },
                  "sender_domain": {
                    "type": "string",
                    "format": "hostname"
                  },
                  "url_domains": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "hostname"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created; carries the existing verdict if the message was already triaged."
          },
          "400": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "401": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "403": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "503": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          }
        }
      },
      "patch": {
        "tags": [
          "Protection"
        ],
        "summary": "Set a triage verdict (owner/admin)",
        "operationId": "setPhishVerdict",
        "description": "The verdict describes a message, never a person. By default it also lands on every pending report of the same message, so each reporter sees it. Audited.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "verdict"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "phishing",
                      "simulation",
                      "spam",
                      "legitimate",
                      "inconclusive"
                    ]
                  },
                  "apply_to_campaign": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated."
          },
          "400": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "401": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "403": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "404": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "503": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          }
        }
      }
    },
    "/api/business/report/monthly": {
      "get": {
        "tags": [
          "Protection"
        ],
        "summary": "Monthly executive security report",
        "operationId": "getMonthlyReport",
        "description": "Aggregate counts only (incidents, payee-change holds, staff phishing reports, simulation training gaps) for one UTC month. A section whose data source is not set up is `null` (\"not measured\"), never zero. `format=html` returns a printable page. Owner/admin session.",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "html"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Report (JSON or HTML)."
          },
          "400": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "401": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "403": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          },
          "503": {
            "description": "Error envelope `{ ok:false, error, field? }`."
          }
        }
      }
    },
    "/api/kyt/v1/batch": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Screen many targets in one call",
        "operationId": "post_api_kyt_v1_batch",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/kyt/v1/batch   (AUTH-GATED, fail-closed, tenant-pinned, metered)\n\nScreen many targets in one call — the same screen→policy→persist→meter→audit→\nwebhook pipeline (./_screen.ts) applied per item, so each item carries the same\nsafety invariants (THE CLAMP, coverage honesty, never-throw) as /screen.\n\n  body { targets: [{chain,address|tx|counterparty,counterparty_type?}, …]  (max 100),\n         policy_id? }\n  Idempotency-Key header → a replayed POST returns the SAME cached body.\n\n  → { ok, results[], billed_units }\n    each result: { ok, [target], screening, policy_action, policy_reason,\n                   human_review_required, deep_link }  OR  { ok:false, error, detail }\n  > 100 targets ⇒ 400 (too_many_targets). Empty ⇒ 400.\n\nMETERED per VALID item (KYT_UNITS=3 each) on the partner path; a rejected item\n(bad target) is never billed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/entities": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Link an entity (wallets, masked fiat ids) to an external uid",
        "operationId": "post_api_kyt_v1_entities",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/entities   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe PARTNER KYT entity-linking collection endpoint — the Chainalysis-Reactor-\nstyle \"link my user to NEXUS\" surface. A partner attaches its own opaque\n`external_uid` (plus crypto wallets and/or fiat identifiers) so it can later\nscreen + monitor + deep-link that entity.\n\n  POST  → linkEntity({external_uid,label,wallets,fiat_ids})\n          201 { ok, entity_id, external_uid, wallets[], fiat_ids:{counts}, deep_link }\n          Idempotency-Key header → a replayed POST returns the SAME cached body.\n  GET   → listEntities (tenant-pinned, newest-first, keyset-paginated)\n          200 { ok, entities[], cursor }\n\nAUTH — fails CLOSED (see ../../nexus/v1/kyt/_auth.ts → resolveKytCaller):\nadmin/operator (CF Access JWT / service token / signed session) OR a partner\nkey (`vlx_live_`, or a legacy `vlx_partner_` during migration) carrying the\n`kyt:screen` scope at a `pro`+ canonical tier. No valid identity ⇒ 403; an\nunder-tier / under-scope partner ⇒ 403. A missing DB ⇒ 503 (honest, never a\nfabricated success).\n\nTENANT — pinned SERVER-side to the resolved caller tenant (`partner:<id>` for\na partner, `default` for an operator). NEVER read from the request body; the\nentities helpers carry `WHERE partner_id = ?` on every query, so a cross-tenant\nuid simply doesn't exist for this caller.\n\nGDPR — fiat identifiers are stored SHA-256 hashed + a display mask only; the\nplaintext is NEVER persisted nor echoed. The POST response returns fiat COUNTS\n(by kind), not values.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "List linked entities",
        "operationId": "get_api_kyt_v1_entities",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/entities   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe PARTNER KYT entity-linking collection endpoint — the Chainalysis-Reactor-\nstyle \"link my user to NEXUS\" surface. A partner attaches its own opaque\n`external_uid` (plus crypto wallets and/or fiat identifiers) so it can later\nscreen + monitor + deep-link that entity.\n\n  POST  → linkEntity({external_uid,label,wallets,fiat_ids})\n          201 { ok, entity_id, external_uid, wallets[], fiat_ids:{counts}, deep_link }\n          Idempotency-Key header → a replayed POST returns the SAME cached body.\n  GET   → listEntities (tenant-pinned, newest-first, keyset-paginated)\n          200 { ok, entities[], cursor }\n\nAUTH — fails CLOSED (see ../../nexus/v1/kyt/_auth.ts → resolveKytCaller):\nadmin/operator (CF Access JWT / service token / signed session) OR a partner\nkey (`vlx_live_`, or a legacy `vlx_partner_` during migration) carrying the\n`kyt:screen` scope at a `pro`+ canonical tier. No valid identity ⇒ 403; an\nunder-tier / under-scope partner ⇒ 403. A missing DB ⇒ 503 (honest, never a\nfabricated success).\n\nTENANT — pinned SERVER-side to the resolved caller tenant (`partner:<id>` for\na partner, `default` for an operator). NEVER read from the request body; the\nentities helpers carry `WHERE partner_id = ?` on every query, so a cross-tenant\nuid simply doesn't exist for this caller.\n\nGDPR — fiat identifiers are stored SHA-256 hashed + a display mask only; the\nplaintext is NEVER persisted nor echoed. The POST response returns fiat COUNTS\n(by kind), not values."
      }
    },
    "/api/kyt/v1/history": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "Page a partner's screening history from `screening_results`",
        "operationId": "get_api_kyt_v1_history",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "GET /api/kyt/v1/history   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPage a partner's screening history from `screening_results` — every query is\n`WHERE partner_id = ?` (the server-resolved tenant), so a partner only ever\nsees its OWN screens. Keyset-paginated on created_at via an opaque cursor.\n\n  ?uid=…       filter to a linked entity (resolves uid → entity_id, tenant-pinned)\n  ?address=…   filter to a target_ref (canonical lowercase for EVM)\n  ?verdict=…   allow | review | block (filters on the policy action)\n  ?limit=…     1..200 (default 50)\n  ?cursor=…    the created_at of the last row seen (older rows fetched next)\n\n  → { ok, results[], cursor }\n\nThe result rows are the PII-free stored projection (verdict, policy_action,\nscores, coverage, category tags) — never a counterparty's PII. A missing DB\n⇒ 503; an unknown uid ⇒ an empty page (not a 404 — a history query of an\nabsent entity is honestly \"no screens\")."
      }
    },
    "/api/kyt/v1/monitor": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Subscribe a target to continuous monitoring",
        "operationId": "post_api_kyt_v1_monitor",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/monitor   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe CONTINUOUS-MONITORING half of the partner KYT platform. A partner\nsubscribes a linked entity (by its opaque `uid`) OR a bare wallet `address`\nto ongoing re-screening:\n\n  POST /api/kyt/v1/monitor\n    { uid?, address?, chain?, policy_id?, cadence?, notify? }\n    → upserts a `monitoring_subscriptions` row (tenant-pinned) AND enrolls each\n      of the entity's (or the bare) wallet(s) into the EXISTING NEXUS watchlist\n      via `addWatch` — it does NOT build a new monitor. The cron\n      (/api/cron/nexus/monitor → runMonitor) re-screens those watches and the\n      monitor emits a `kyt.alert` partner webhook + applies the entity policy on\n      a material change.\n\n  GET  /api/kyt/v1/monitor          → list this partner's subscriptions.\n\nAUTH — reuses `resolveKytCaller` (admin/operator via Cloudflare Access OR a\n`vlx_live_` partner key carrying `kyt:screen` at a `pro`+ tier). Fails CLOSED:\nno valid key + not admin ⇒ 403; DB unbound ⇒ 503. The tenant is ALWAYS\nserver-derived (`partner:<id>` / `default`), NEVER read from the client body.\n\nThe `policy_id` (when supplied) is the entity's risk policy the cron applies on\na material change; it is validated to belong to the SAME partner (a cross-tenant\npolicy id is rejected) before the subscription is written.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "List continuous-monitoring subscriptions",
        "operationId": "get_api_kyt_v1_monitor",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/monitor   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe CONTINUOUS-MONITORING half of the partner KYT platform. A partner\nsubscribes a linked entity (by its opaque `uid`) OR a bare wallet `address`\nto ongoing re-screening:\n\n  POST /api/kyt/v1/monitor\n    { uid?, address?, chain?, policy_id?, cadence?, notify? }\n    → upserts a `monitoring_subscriptions` row (tenant-pinned) AND enrolls each\n      of the entity's (or the bare) wallet(s) into the EXISTING NEXUS watchlist\n      via `addWatch` — it does NOT build a new monitor. The cron\n      (/api/cron/nexus/monitor → runMonitor) re-screens those watches and the\n      monitor emits a `kyt.alert` partner webhook + applies the entity policy on\n      a material change.\n\n  GET  /api/kyt/v1/monitor          → list this partner's subscriptions.\n\nAUTH — reuses `resolveKytCaller` (admin/operator via Cloudflare Access OR a\n`vlx_live_` partner key carrying `kyt:screen` at a `pro`+ tier). Fails CLOSED:\nno valid key + not admin ⇒ 403; DB unbound ⇒ 503. The tenant is ALWAYS\nserver-derived (`partner:<id>` / `default`), NEVER read from the client body.\n\nThe `policy_id` (when supplied) is the entity's risk policy the cron applies on\na material change; it is validated to belong to the SAME partner (a cross-tenant\npolicy id is rejected) before the subscription is written."
      }
    },
    "/api/kyt/v1/policies": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "List risk policies",
        "operationId": "get_api_kyt_v1_policies",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/policies   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe partner-facing CRUD for KYT risk POLICIES — the thresholds + per-category\nrules + default action + on-block behaviour that the screening pipeline runs a\nverdict through (via _shared/kyt/policy.ts evaluatePolicy) to derive the\nallow/review/BLOCK action. This route ONLY stores/lists policies; it never\nevaluates and CANNOT bypass the person/high-stakes CLAMP, which lives, sacred,\ninside evaluatePolicy.\n\n  GET  → list this partner's policies (default first).\n  POST → create/update a named policy:\n           { name, thresholds:{block_at,review_at}, category_rules[],\n             default_action, on_block:{webhook?,enqueue?}, make_default? }\n         Upsert on (partner_id, name). category_rules are VALIDATED — a\n         malformed rule is dropped, never silently widening a block.\n\nAUTH — fails CLOSED, exactly like the screen route: an admin/operator\n(Cloudflare Access / signed session) OR a `vlx_live_` partner key carrying the\n`kyt:screen` scope at the `pro`+ canonical tier. No identity ⇒ 403; an\nunder-tier/under-scope key ⇒ 403. The tenant is ALWAYS server-derived\n(`partner:<id>` for a partner, the operator tenant for an admin) and pinned\ninto every query — NEVER read from the request body. DB unbound ⇒ 503.\n\nEvery mutation writes an append-only `kyt_audit` row (PII-free target_ref)."
      },
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Create a risk policy",
        "operationId": "post_api_kyt_v1_policies",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/policies   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe partner-facing CRUD for KYT risk POLICIES — the thresholds + per-category\nrules + default action + on-block behaviour that the screening pipeline runs a\nverdict through (via _shared/kyt/policy.ts evaluatePolicy) to derive the\nallow/review/BLOCK action. This route ONLY stores/lists policies; it never\nevaluates and CANNOT bypass the person/high-stakes CLAMP, which lives, sacred,\ninside evaluatePolicy.\n\n  GET  → list this partner's policies (default first).\n  POST → create/update a named policy:\n           { name, thresholds:{block_at,review_at}, category_rules[],\n             default_action, on_block:{webhook?,enqueue?}, make_default? }\n         Upsert on (partner_id, name). category_rules are VALIDATED — a\n         malformed rule is dropped, never silently widening a block.\n\nAUTH — fails CLOSED, exactly like the screen route: an admin/operator\n(Cloudflare Access / signed session) OR a `vlx_live_` partner key carrying the\n`kyt:screen` scope at the `pro`+ canonical tier. No identity ⇒ 403; an\nunder-tier/under-scope key ⇒ 403. The tenant is ALWAYS server-derived\n(`partner:<id>` for a partner, the operator tenant for an admin) and pinned\ninto every query — NEVER read from the request body. DB unbound ⇒ 503.\n\nEvery mutation writes an append-only `kyt_audit` row (PII-free target_ref).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/reports": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Generate a partner KYT report grounded in stored, PII-free screening data",
        "operationId": "post_api_kyt_v1_reports",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/kyt/v1/reports   (AUTH-GATED, fail-closed, tenant-pinned)\n\nGenerate a partner KYT report grounded in stored, PII-free screening data.\n\n  Body: { kind: \"exposure\" | \"sar_draft\" | \"entity_summary\",\n          uid?: string,        // a linked entity's opaque uid\n          address?: string,    // OR an ad-hoc target ref\n          format?: \"json\" | \"csv\" }\n  → 200 { ok: true, report_id, status, kind, format }\n\nThe report is built from `screening_results` (a PII-free KytScreening\nsnapshot) + masked fiat ids only — it NEVER reads a fiat plaintext or hash,\nand NEVER fabricates an \"AI-written\" SAR narrative (sar_draft is a\ndeterministic, grounded skeleton an analyst completes). It is stored in\nkyt_reports and fetched via GET /api/kyt/v1/reports/:id.\n\nAUTH — fails CLOSED (resolveKytCaller): admin/operator OR a `vlx_live_`\npartner key carrying `kyt:screen` at a `pro`+ canonical tier. No valid key +\nnot admin ⇒ 403. DB unbound ⇒ 503.\n\nTENANT — pinned server-side (partner:<id> for a partner, the operator tenant\nfor an admin). Every query is `WHERE partner_id = ?`; the tenant is NEVER read\nfrom the client body, so a partner can only report on its OWN entities/screens.\n\nMETERED — a PARTNER report records 1 unit into partner_api_usage (an admin\nreport is an internal lookup, not metered). Never throws into the response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/screen": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Screen one target (partner KYT)",
        "operationId": "post_api_kyt_v1_screen",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/kyt/v1/screen   (AUTH-GATED, fail-closed, tenant-pinned, metered)\n\nThe Partner KYT screening endpoint — wraps the REAL NEXUS KYT engine + the\npure policy engine into the saleable Protect API call.\n\n  body {\n    target: { chain, address | tx | counterparty, counterparty_type? },\n    or_uid?: string,          // screen a LINKED entity's wallets instead (cap 25)\n    policy_id?: string,       // a specific policy; else the partner's default\n    persist?: boolean         // default true — write to screening_results\n  }\n  Idempotency-Key header → a replayed POST returns the SAME cached body.\n\n  → { ok, screening, policy_action, policy_reason, human_review_required,\n      deep_link, billed_units }\n  (or for or_uid: { ok, uid, results[], billed_units })\n\nPIPELINE (see ./_screen.ts): screen → evaluatePolicy → persist → meter →\naudit → on block emit a SIGNED `kyt.block` webhook. THE CLAMP (a person /\nhigh-stakes is never auto-blocked) lives in policy.ts and is never bypassed.\n\nAUTH — fails CLOSED (resolveKytCaller): admin OR a `vlx_live_` partner key with\nthe `kyt:screen` scope at a `pro`+ tier. No valid identity ⇒ 403; DB unbound\n⇒ 503. METERED on the partner path only (KYT_UNITS=3 per wallet).\n\nTENANT pinned server-side (`auth.caller.tenant`); NEVER read from the body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/usage": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "The partner's own KYT metering dashboard data",
        "operationId": "get_api_kyt_v1_usage",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "GET /api/kyt/v1/usage   (AUTH-GATED, fail-closed, tenant-pinned)\n\nThe partner's own KYT metering dashboard data: this calendar-month metered-unit\nconsumption, the tier quota, and a per-endpoint breakdown — read straight from\n`partner_api_usage` (the same table recordUsage writes + the #135 metered\nbilling layer reads). Mirrors GET /api/partner/v1/usage, but on the KYT group's\nfail-closed caller (admin OR a `vlx_live_` kyt:screen partner key, pro+).\n\n  → 200 { ok, tier, period, quota: { monthly_quota_units, used_units,\n          remaining_units, rate_limit_rpm }, total_calls, by_endpoint[] }\n\nAUTH — fails CLOSED (resolveKytCaller). TENANT — pinned server-side; every\nquery is `WHERE partner_id = ?`, never from the client, so a partner sees only\nits OWN usage. This introspection read is itself NOT metered."
      }
    },
    "/api/kyt/v1/webhooks": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "List signed webhook endpoints",
        "operationId": "get_api_kyt_v1_webhooks",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/webhooks   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPartner-facing management of SIGNED outbound webhook subscriptions for the\nKYT Protect platform. A partner registers an HTTPS endpoint; Vilkax then\ndelivers signed POSTs for the KYT lifecycle events\n  kyt.screening · kyt.alert · kyt.block · kyt.report.ready\n(plus a kyt.test event from the /test sub-route). Delivery + signing reuse\nthe EXISTING `_shared/webhooks/deliver.ts` over the EXISTING\nwebhook_endpoints / webhook_deliveries tables (migration 0161). We do NOT\nfork a second webhook store.\n\n  GET  → list this tenant's endpoints. The signing SECRET is NEVER returned\n         (only id / url / events / enabled / created_at) — a leaked secret\n         would let anyone forge a valid signature.\n  POST → register a new endpoint.\n           { url (https only), events (comma-sep of the kyt.* set or '*'),\n             secret? (>= 16 chars; auto-generated + returned ONCE if absent) }\n         The generated secret is the ONLY time the plaintext leaves the\n         server — it is stored as-is and never echoed again.\n\nAUTH — fails CLOSED via resolveKytCaller (admin OR a vlx_live_ partner key\ncarrying kyt:screen at pro+). No valid caller ⇒ 403. The tenant is ALWAYS\nserver-derived (auth.caller.tenant), NEVER taken from the client body.\n\nDB unbound ⇒ 503 (honest); never throws at the top level."
      },
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Register a signed webhook endpoint",
        "operationId": "post_api_kyt_v1_webhooks",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/webhooks   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPartner-facing management of SIGNED outbound webhook subscriptions for the\nKYT Protect platform. A partner registers an HTTPS endpoint; Vilkax then\ndelivers signed POSTs for the KYT lifecycle events\n  kyt.screening · kyt.alert · kyt.block · kyt.report.ready\n(plus a kyt.test event from the /test sub-route). Delivery + signing reuse\nthe EXISTING `_shared/webhooks/deliver.ts` over the EXISTING\nwebhook_endpoints / webhook_deliveries tables (migration 0161). We do NOT\nfork a second webhook store.\n\n  GET  → list this tenant's endpoints. The signing SECRET is NEVER returned\n         (only id / url / events / enabled / created_at) — a leaked secret\n         would let anyone forge a valid signature.\n  POST → register a new endpoint.\n           { url (https only), events (comma-sep of the kyt.* set or '*'),\n             secret? (>= 16 chars; auto-generated + returned ONCE if absent) }\n         The generated secret is the ONLY time the plaintext leaves the\n         server — it is stored as-is and never echoed again.\n\nAUTH — fails CLOSED via resolveKytCaller (admin OR a vlx_live_ partner key\ncarrying kyt:screen at pro+). No valid caller ⇒ 403. The tenant is ALWAYS\nserver-derived (auth.caller.tenant), NEVER taken from the client body.\n\nDB unbound ⇒ 503 (honest); never throws at the top level.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/policies/{id}/default": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Make policy `:id` the partner's single DEFAULT policy",
        "operationId": "post_api_kyt_v1_policies_id_default",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/kyt/v1/policies/:id/default   (AUTH-GATED, fail-closed, tenant-pinned)\n\nMake policy `:id` the partner's single DEFAULT policy — the one the screening\npipeline evaluates against when no explicit policy is named. Sets is_default=1\non the target and clears it on all of the partner's OTHER policies, so exactly\none default ever exists (enforced in _shared/kyt/policies.setDefaultPolicy).\n\nAUTH — fails CLOSED (admin/operator OR a `vlx_live_` partner key with the\n`kyt:screen` scope at the `pro`+ tier). The tenant is server-derived and\npinned into every query: a partner can only default-flag ITS OWN policy. A\nmissing/cross-tenant id ⇒ 404 (never an implicit create, never a cross-tenant\nwrite). DB unbound ⇒ 503. Writes an append-only kyt_audit row.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/monitor/{id}": {
      "delete": {
        "tags": [
          "KYT"
        ],
        "summary": "Unsubscribe a continuous-monitoring subscription",
        "operationId": "delete_api_kyt_v1_monitor_id",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "DELETE /api/kyt/v1/monitor/:id   (AUTH-GATED, fail-closed, tenant-pinned)\n\nUnsubscribe a continuous-monitoring subscription. Deletes the\n`monitoring_subscriptions` row (tenant-pinned: a cross-tenant id deletes\nnothing → 404) and PAUSES the NEXUS watchlist rows it enrolled (matched by the\nsubscription id encoded on the watch label, scoped to the caller's tenant) so\nthe cron stops re-screening them.\n\nAUTH — `resolveKytCaller` (admin OR a `vlx_live_` partner key, kyt scope,\npro+ tier). Fails CLOSED. The tenant is server-derived; the :id is matched\nWHERE partner_id = <server tenant>, never trusting the path alone.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/kyt/v1/entities/{uid}": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "Get a linked entity",
        "operationId": "get_api_kyt_v1_entities_uid",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/entities/[uid]   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPer-entity operations on a partner's linked user (its opaque `external_uid`):\n\n  GET    → getEntity → full entity { wallets, MASKED fiat_ids, last_screening,\n           deep_link }. 404 for a missing OR cross-tenant uid (no existence\n           oracle across tenants).\n  PATCH  → patchEntity → add/remove wallets + fiat ids, optionally relabel.\n           200 with the updated entity, or 404 (never an implicit create).\n  DELETE → deleteEntity → GDPR hard-purge of the entity + ALL child rows\n           (wallets, hashed fiat, screening history). 200 { ok } even when the\n           uid was already absent (idempotent erasure).\n\nAUTH fails CLOSED (resolveKytCaller); DB unbound ⇒ 503. The tenant is pinned\nSERVER-side to the resolved caller tenant and passed to every helper as\npartner_id — the [uid] path param is the entity key, never the tenant. GDPR:\nfiat is returned masked only; plaintext is never persisted nor echoed.",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "patch": {
        "tags": [
          "KYT"
        ],
        "summary": "Update a linked entity",
        "operationId": "patch_api_kyt_v1_entities_uid",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/entities/[uid]   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPer-entity operations on a partner's linked user (its opaque `external_uid`):\n\n  GET    → getEntity → full entity { wallets, MASKED fiat_ids, last_screening,\n           deep_link }. 404 for a missing OR cross-tenant uid (no existence\n           oracle across tenants).\n  PATCH  → patchEntity → add/remove wallets + fiat ids, optionally relabel.\n           200 with the updated entity, or 404 (never an implicit create).\n  DELETE → deleteEntity → GDPR hard-purge of the entity + ALL child rows\n           (wallets, hashed fiat, screening history). 200 { ok } even when the\n           uid was already absent (idempotent erasure).\n\nAUTH fails CLOSED (resolveKytCaller); DB unbound ⇒ 503. The tenant is pinned\nSERVER-side to the resolved caller tenant and passed to every helper as\npartner_id — the [uid] path param is the entity key, never the tenant. GDPR:\nfiat is returned masked only; plaintext is never persisted nor echoed.",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "KYT"
        ],
        "summary": "Unlink an entity",
        "operationId": "delete_api_kyt_v1_entities_uid",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/kyt/v1/entities/[uid]   (AUTH-GATED, fail-closed, tenant-pinned)\n\nPer-entity operations on a partner's linked user (its opaque `external_uid`):\n\n  GET    → getEntity → full entity { wallets, MASKED fiat_ids, last_screening,\n           deep_link }. 404 for a missing OR cross-tenant uid (no existence\n           oracle across tenants).\n  PATCH  → patchEntity → add/remove wallets + fiat ids, optionally relabel.\n           200 with the updated entity, or 404 (never an implicit create).\n  DELETE → deleteEntity → GDPR hard-purge of the entity + ALL child rows\n           (wallets, hashed fiat, screening history). 200 { ok } even when the\n           uid was already absent (idempotent erasure).\n\nAUTH fails CLOSED (resolveKytCaller); DB unbound ⇒ 503. The tenant is pinned\nSERVER-side to the resolved caller tenant and passed to every helper as\npartner_id — the [uid] path param is the entity key, never the tenant. GDPR:\nfiat is returned masked only; plaintext is never persisted nor echoed.",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/kyt/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "KYT"
        ],
        "summary": "Remove one of the calling partner's signed-webhook endpoints",
        "operationId": "delete_api_kyt_v1_webhooks_id",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "DELETE /api/kyt/v1/webhooks/:id   (AUTH-GATED, fail-closed, tenant-pinned)\n\nRemove one of the calling partner's signed-webhook endpoints. The delete is\ntenant-pinned: the WHERE clause carries BOTH the id AND the server-derived\ntenant, so a partner can never delete another tenant's endpoint by guessing\nan id (cross-tenant isolation). Idempotent — deleting an absent/foreign id\nreturns ok:true with deleted:false (no 404 leak about whether an id exists\nin another tenant).\n\nAUTH — fails CLOSED via resolveKytCaller (admin OR a vlx_live_ partner key\nat pro+). DB unbound ⇒ 503. Never throws at the top level.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/kyt/v1/webhooks/{id}/test": {
      "post": {
        "tags": [
          "KYT"
        ],
        "summary": "Send a signed kyt.test event to an endpoint",
        "operationId": "post_api_kyt_v1_webhooks_id_test",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/kyt/v1/webhooks/:id/test   (AUTH-GATED, fail-closed, tenant-pinned)\n\nFire a SIGNED `kyt.test` event at the partner's stored endpoint so they can\nverify their receiver + signature-verification BEFORE going live. The\nendpoint is loaded tenant-pinned (id AND server-derived tenant), then\ndelivered via the EXISTING signed-webhook deliverer (`_shared/webhooks/\ndeliver.ts`) using the endpoint's STORED secret — the secret is never read\nfrom, nor returned to, the client.\n\nThe result reports honestly whether the receiver accepted the signed POST\n(ok / http status / attempts / error) — a flaky or rejecting receiver yields\ndelivered:false, never a fabricated success. deliverWebhook never throws and\nrecords a webhook_deliveries audit row.\n\nAUTH — fails CLOSED via resolveKytCaller (admin OR a vlx_live_ partner key at\npro+). Unknown/foreign id ⇒ 404 (tenant-pinned lookup). DB unbound ⇒ 503.\nNever throws at the top level.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/kyt/v1/reports/{id}": {
      "get": {
        "tags": [
          "KYT"
        ],
        "summary": "Fetch a previously-generated KYT report",
        "operationId": "get_api_kyt_v1_reports_id",
        "security": [
          {
            "bearerAuth": [
              "kyt:screen"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "GET /api/kyt/v1/reports/:id   (AUTH-GATED, fail-closed, tenant-pinned)\n\nFetch a previously-generated KYT report. Returns the stored PII-free payload\nas JSON, OR — when the report was generated with format:\"csv\" (or the caller\npasses ?format=csv) — a CSV download (text/csv, attachment).\n\n  → 200 application/json  { ok: true, report: { id, kind, status, format, generated_at, payload } }\n  → 200 text/csv          (Content-Disposition: attachment; filename=\"kyt_<kind>_<id>.csv\")\n  → 404 when the id is unknown OR belongs to ANOTHER tenant (no leak).\n\nAUTH — fails CLOSED (resolveKytCaller): admin OR a `vlx_live_` partner key\n(kyt:screen, pro+). TENANT — pinned server-side; a partner can only read its\nOWN reports (the query is `WHERE partner_id = ? AND id = ?`). A cross-tenant id\nis a 404, never another tenant's data.\n\nThis GET is an introspection read — NOT metered.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/query": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Query the NEXUS entity index",
        "operationId": "get_api_nexus_query",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/nexus/v1/assess": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS assessment endpoint",
        "operationId": "post_api_nexus_v1_assess",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/assess  (AUTH-GATED, fail-closed)\n\nThe NEXUS assessment endpoint. Given an entity query it gathers enrichment + the\nexposure-graph neighbourhood, derives the transparent features, computes the\nreasoned exposure score, and returns the assessment:\n\n  { entity, entity_type, risk_score, confidence, exposures[], reasons[],\n    graph_summary, human_review_required, advisory }\n\nAUTH — this is an INVESTIGATIVE endpoint, so it FAILS CLOSED. An operator on\nALLOWED_ADMIN_EMAILS is required, via `requireAdminOrSession` (a Cloudflare-\nAccess JWT, an allow-listed CF Access service token, OR a signed Vilkax\ndashboard session cookie); a missing / unconfigured allowlist denies (never\nopens). This mirrors the SAR endpoint's posture and the CLAUDE.md \"fail\nCLOSED on auth\" rule.\n\nSAFETY — for PEOPLE and other high-stakes entities the assessment is ADVISORY:\n`human_review_required` is true, the framing is assessment-not-accusation, and\nNO automated action is implied. NEXUS is decision-SUPPORT for an analyst.\n\nGET returns 405; only POST is supported.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/attribution": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The prediction layer",
        "operationId": "post_api_nexus_v1_attribution",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/attribution  (AUTH-GATED, fail-closed)\n\nThe prediction layer: from observed signals (tags, coinjoin prob, cluster\nsize, counterparties, exposure categories, enrichment labels) it generates\nranked attribution HYPOTHESES — each with supporting AND contradictory\nevidence, a confidence, and an explanation.\n\nSAFETY (the cardinal rule): a hypothesis is NEVER a fact. A person /\nhigh-stakes attribution carries `requires_human_review = true` and must be\nrouted through the analyst/advisory framing — no automated action, no\naccusation. Pass `is_person: true` to force the human-review framing.\n\nBody: AttributionSignals. AUTH — FAILS CLOSED via `requireAdminOrSession` (an\noperator on the allowlist: CF-Access JWT / service token / signed dashboard\nsession cookie). GET → 405.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/clustering": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Runs the UTXO clustering heuristics",
        "operationId": "post_api_nexus_v1_clustering",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/clustering  (AUTH-GATED, fail-closed)\n\nRuns the UTXO clustering heuristics. Two modes:\n  • { tx }           — single-tx common-input-ownership + change linkage.\n  • { tx_a, tx_b }   — cross-transaction fingerprint / reuse / temporal links.\n\nReturns the proposed cluster + every confidence-scored link + evidence. The\nengine REFUSES to assert ownership and SUPPRESSES clustering on a CoinJoin.\n\nAUTH — investigative, FAILS CLOSED via `requireAdminOrSession` (an operator on\nthe allowlist: CF-Access JWT / service token / signed dashboard session\ncookie). GET → 405.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/counterparties": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS Counterparties tab endpoint",
        "operationId": "post_api_nexus_v1_counterparties",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/counterparties  (AUTH-GATED, fail-closed)\n\nThe NEXUS Counterparties tab endpoint.  Given a wallet entity + a transfer\nneighbourhood, returns:\n\n  • `counterparties` — every address the subject transacted with, ranked by\n    total volume (sent_value + received_value), with direction/counts/timing.\n  • `pair_detail`    — optional full A↔B breakdown when a specific\n    `counterparty` address is also supplied (the \"click an edge\" feature).\n  • Per-counterparty `ownership` hint via `predictOwnerType` — an\n    analyst-facing label (\"exchange / individual / mixer / …\").\n\nAnalyst posture: counterparty data is OBSERVATION (who the wallet transacted\nwith), never an accusation.  All values in smallest-unit (sats/wei); no USD\nconversions (we don't have a price oracle here).\n\nAUTH: same fail-closed gate as /assess — requireAdminOrSession.\n\nRequest body:\n  {\n    entity:        string,         // the wallet address being investigated\n    chain:         \"btc\"|\"eth\"|\"sol\",\n    transfers:     Transfer[],     // the pre-projected transfer neighbourhood\n    counterparty?: string,         // optional — triggers pair_detail for this addr\n    limit?:        number,         // max counterparties returned (default 100)\n  }\n\nResponse 200:\n  {\n    ok: true,\n    entity: string,\n    chain: string,\n    counterparties: CounterpartyResult[],\n    pair_detail?: PairDetail,      // present when `counterparty` was supplied\n    total: number,\n    requested_by: string,\n  }",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/coverage": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Surfaces, WITHOUT running a screen, exactly which OSINT/chain sources are",
        "operationId": "get_api_nexus_v1_coverage",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "GET /api/nexus/v1/coverage   (platform-level data-coverage status)\n\nSurfaces, WITHOUT running a screen, exactly which OSINT/chain sources are\nLIVE vs which need an owner-supplied key (holistic audit P0-4/N5). So an\nevaluator sees honest coverage on day one — \"dark\" reads as \"set these keys\",\nnever \"broken\" — and the KYT console renders a platform \"data coverage\"\nstatus banner.\n\nAUTH — fails CLOSED. Admin (Cloudflare Access) OR a valid partner key (the\nsame canonical resolver KYT uses). The status reveals which secrets are set,\nso it's gated; an unauthenticated caller gets 403. No metering (it's a status\nread, not a screen)."
      }
    },
    "/api/nexus/v1/entity": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS ENTITY-PANEL endpoint",
        "operationId": "post_api_nexus_v1_entity",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/entity   (AUTH-GATED, fail-closed)\n\nThe NEXUS ENTITY-PANEL endpoint — the Reactor-style right-side panel's data\ncontract, computed from REAL persisted data (`graph_store.ts` /\n`nexus_graph_edges`, migrations 0126 + 0237). It returns ONE envelope the\napex-standalone / business-desktop NexusWorkspace renders directly:\n\n  • summary  — cluster id, activity range, balance, sent, received, total fees\n               + label/category/status. All money fields HONEST NULL when no\n               real figure exists (never a fabricated 0 / estimate).\n  • exposure — { receiving, sending }, BOTH REAL: receiving slices come from\n               INBOUND edges grouped by counterparty category, sending slices\n               from OUTBOUND edges. NEVER sending = receiving × 0.8 (the\n               fabricated-sending bug this replaces).\n  • chains   — the chains the entity has activity on (the chain selector).\n  • assets   — the native asset of each chain (always computable) + an honest\n               `requires_node_seam` flag because full per-token enumeration\n               needs the live indexer/balance SEAM (not wired) — never an\n               invented token balance.\n\nBody: { entity: string, fanout?: number }   (entity = the address/cluster key)\n\nAUTH — investigative/operator surface. FAILS CLOSED via `resolvePartnerOrAdmin`\n(the SAME helper /graph + /search use): an admin/operator on\n`ALLOWED_ADMIN_EMAILS` (CF-Access JWT / service token / signed session cookie)\nOR a valid partner key OR a verified business-suite session\n(`allowBusinessSession`). No identity ⇒ 403, never opens.\n\nSAFETY — the panel measures LINKS + FLOWS over public/lawful data, never an\nidentity verdict about a person. READ-ONLY (no writes/actions). Never throws a\n500 from the compute path — degrades t",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/exposure": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Exposure engine v2: given a set of categorised exposure paths (each with",
        "operationId": "post_api_nexus_v1_exposure",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/exposure  (AUTH-GATED, fail-closed)\n\nExposure engine v2: given a set of categorised exposure paths (each with\npath + amount + distance + confidence + optional timestamp), aggregates them\ninto the per-category exposure model (direct/indirect, value-/time-/\nconfidence-weighted) + a composite 0..100 score. The structured output is what\na UI renders as wheels / trees / paths / timelines (data, not viz).\n\nBody: { paths: ExposurePath[], now?: number, half_life_sec?: number }\n\nAUTH — investigative, FAILS CLOSED via `requireAdminOrSession` (an operator on\nthe allowlist: CF-Access JWT / service token / signed dashboard session\ncookie). SAFETY — exposure is a measure of links + flows, never guilt. GET → 405.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/feedback": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Record analyst feedback on a NEXUS decision",
        "operationId": "post_api_nexus_v1_feedback",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/feedback  (AUTH-GATED, fail-closed)\nGET  /api/nexus/v1/feedback  → accumulated dataset stats (auth-gated)\n\nThe analyst-feedback loop. An authenticated analyst records the OUTCOME of a\nNEXUS assessment (confirmed_risk / false_positive / unknown) together with\nthe frozen feature + score snapshot, turning it into a labelled training\nexample (migration 0127). GET returns the dataset-readiness stats an owner\nchecks before an offline retrain.\n\nAUTH — fails CLOSED via `requireAdminOrSession` (an operator on the allowlist:\nCF-Access JWT / service token / signed dashboard session cookie), same as /assess.\n\nThis INSTRUMENTS the dataset; it does NOT train a model. The fit (XGBoost /\nLightGBM / GNN) is an owner/offline step — see docs/nexus/EXPOSURE_GRAPH.md.\nA recorded outcome judges an assessment, never a person, and authorises no\nautomated action.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Feedback dataset statistics",
        "operationId": "get_api_nexus_v1_feedback",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/feedback  (AUTH-GATED, fail-closed)\nGET  /api/nexus/v1/feedback  → accumulated dataset stats (auth-gated)\n\nThe analyst-feedback loop. An authenticated analyst records the OUTCOME of a\nNEXUS assessment (confirmed_risk / false_positive / unknown) together with\nthe frozen feature + score snapshot, turning it into a labelled training\nexample (migration 0127). GET returns the dataset-readiness stats an owner\nchecks before an offline retrain.\n\nAUTH — fails CLOSED via `requireAdminOrSession` (an operator on the allowlist:\nCF-Access JWT / service token / signed dashboard session cookie), same as /assess.\n\nThis INSTRUMENTS the dataset; it does NOT train a model. The fit (XGBoost /\nLightGBM / GNN) is an owner/offline step — see docs/nexus/EXPOSURE_GRAPH.md.\nA recorded outcome judges an assessment, never a person, and authorises no\nautomated action."
      }
    },
    "/api/nexus/v1/forensics": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS forensic-heuristics endpoint",
        "operationId": "post_api_nexus_v1_forensics",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/forensics  (AUTH-GATED, fail-closed)\n\nThe NEXUS forensic-heuristics endpoint — Chainalysis-Reactor-parity analysis\nthe rest of the suite (trace / graph / KYT / copilot) composes. One front door\nfor the address/transaction-level forensic techniques, dispatched by `mode`:\n\n  • gas_funding     — EVM \"who funded the gas\" common-control signal.\n  • dex_swap        — balance-change beneficiary + WETH wrap/unwrap collapse.\n  • contract_risk   — counterfeit-token + code-level honeypot + owner-function risk.\n  • wallet_software — UTXO wallet-software fingerprint (Electrum/hardware/…).\n  • tron_energy     — Tron energy delegation hub detection.\n  • utxo_demix      — BTC mixer deposit→withdrawal candidate matching.\n  • tornado_demix   — Tornado Cash pool→withdrawal candidates.\n  • nested_service  — nested-exchange / VASPs-within-VASPs hop detection.\n  • service_profile — identify the service category of an address.\n  • cross_chain     — bridge continuity validation + chain-hop risk.\n  • approval_drainer — ERC-20/NFT unlimited-approval drainer detection.\n  • chain_hopping   — rapid multi-chain hop risk scoring.\n  • address_poisoning — dust-lookalike poisoning candidate detection.\n  • spam_noise      — filter spam/dust/message transfers from a flow.\n  • thorchain_memo  — parse THORChain / OP_RETURN memos.\n  • demix_reconnect — post-mix reconnection (contamination / consolidation /\n                      toxic-change / peel-chain).\n  • flash_loan      — EVM atomic borrow→exploit→repay attack fingerprint.\n  • nft_analysis    — NFT wash-trading cycles + rug-pull deployer drain.\n  • pump_dump       — ERC-20 coordinated accumulation→distribution detection.\n  • token_honeypot  — buy-only trap: buyers received the token but can't sell.\n\nSOURCING — same seam as /trace: a C",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/graph": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS GRAPH-OPERATIONS API",
        "operationId": "post_api_nexus_v1_graph",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/graph   (AUTH-GATED, fail-closed)\n\nThe NEXUS GRAPH-OPERATIONS API — a thin, render-ready front door over the\nREAL exposure-graph store (`_shared/intel/graph_store.ts` /\n`nexus_graph_nodes` + `nexus_graph_edges`, migration 0126). It does NOT\ncompute the graph — it only DISPATCHES to graph_store's already-BOUNDED\ntraversal primitives and shapes the result into one envelope a UI can render\ndirectly (G6 / cytoscape-style { nodes, edges }).\n\nBody: {\n  op: 'neighbors' | 'expand' | 'subgraph' | 'path',\n  ref?: { type, key } | node?: { type, key },   // the seed entity\n  to?:  { type, key },                           // 'path' target\n  depth?: number,                                // capped <= 3\n  limit?: number,                                // node cap, capped <= 500\n  edge_types?: RelType[],                        // optional rel-type filter\n}\n\n  • 'neighbors' — the one-hop neighbourhood of the seed (bounded fanout).\n  • 'expand' / 'subgraph' — a bounded BFS out to `depth`, capped at `limit`\n    visited nodes (expand defaults to a tighter depth; subgraph is the same\n    primitive at the requested depth). NEVER an unbounded scan.\n  • 'path' — the shortest bounded path from `ref` to `to` (BFS parent walk +\n    a `shortestDistanceTo` cross-check). Returns just the nodes/edges on that\n    path, or an empty graph + distance:null when no path exists in-bound.\n\nEvery node + edge carries confidence + provenance, taken HONESTLY from what\ngraph_store actually stored (node `props.confidence`/`props.source`,\nedge `weight` as link-strength + `source` as the producing connector). A\nvalue that wasn't stored is `null` — NEVER fabricated.\n\nAUTH — investigative/operator surface. FAILS CLOSED via\n`resolvePartnerOrAdmin` (the SAME helper search.ts uses): an admin/operator o",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/investigate": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS UNIFIED-INVESTIGATION endpoint",
        "operationId": "post_api_nexus_v1_investigate",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/investigate  (AUTH-GATED, fail-closed, rate-limited)\n\nThe NEXUS UNIFIED-INVESTIGATION endpoint — \"run the whole methodology\". One\ncall fuses the exposure core (enrichment + graph + score + UTXO intel + trace)\nwith every applicable forensic analyzer (gas-funding, DEX-redirection, nested-\nservice, service-profile, approval-drainer, address-poisoning, drainer-victims\npivot), filters spam/dust/message NOISE out of the flow narrative, builds a\nlegal-request strategy, and returns ONE confidence-scored dossier with\nmethodology-driven recommended next steps.\n\nThis is the premium front door over `_shared/intel/investigation.runInvestigation`.\nIt supersedes calling /assess + a fan of /forensics modes by hand.\n\nSOURCING — same seam as /forensics + /trace: the neighbourhood arrives as\ncaller-supplied `transfers` (normalized) or `txs` (per-chain, projected here).\nEVM drainer context (`approvals` / `nft_approvals` / `permits` / cohort) is\nindexer-decoded input — the analyzers are pure.\n\nAUTH — investigative SUITE endpoint → fails closed via `resolvePartnerOrAdmin`\n(operator allowlist / `vlx_live_` partner key / business-suite STAFF session),\nthe same gate as /forensics + /trace. RATE-LIMITED per caller (this is the\nheaviest NEXUS endpoint — it runs the full analyzer fan + counterparty\nclassification IO).\n\nSAFETY — every finding carries confidence + caveat; people + drainer/scam\nclassifications stay human-review; no automated action. (CLAUDE.md doctrine.)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/label": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Analyst TP/FP label endpoint for NEXUS decision records",
        "operationId": "post_api_nexus_v1_label",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/label  (AUTH-GATED, fail-closed)\n\nAnalyst TP/FP label endpoint for NEXUS decision records. An authenticated\noperator marks a decision as \"tp\" (true positive), \"fp\" (false positive),\nor \"fn\" (false negative), optionally with a note. The label is upserted into\n`ml_outcomes` (source=analyst) so the trainer sees real analyst verdicts and\ncloses the ML feedback loop.\n\nBody: { decision_id: string, label: \"tp\" | \"fp\" | \"fn\", note?: string }\n\nAUTH — fails CLOSED via `requireAdminOrSession` (CF-Access JWT / service\ntoken / signed dashboard session cookie). Same gate as every other NEXUS\noperator endpoint. Guard: `nexus-operator-auth`.\n\nIdempotent: re-submitting the same decision_id overwrites with the latest\nanalyst verdict (ON CONFLICT UPDATE). A note is capped at 500 chars.\n\nEmits an audit event (domain=nexus, action=decision.label) to the\nglobal_audit_events chain so the analyst action is fully attributable.\n\nThis LABELS existing outcomes; it does NOT train a model. The offline\nretrain step is an owner action — see docs/nexus/EXPOSURE_GRAPH.md.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/osint": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "NEXUS OSINT deep-dive endpoint",
        "operationId": "post_api_nexus_v1_osint",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/osint   (AUTH-GATED, fail-closed)\n\nNEXUS OSINT deep-dive endpoint — aggregates all known intelligence about a\ngiven entity into a single structured dossier:\n\n  • enrichment   — moat labels, sanctions, scam reports, corroboration score\n  • community    — recent community intel posts (approved + pending)\n  • graph_summary — 1-hop neighbourhood summary (node types + risk bands)\n  • notes        — analyst internal notes on this entity\n  • indicators   — derived risk signals (e.g. sanctioned, adverse_media, high_risk)\n  • risk_level   — 'none' | 'low' | 'medium' | 'high' | 'critical'\n  • sources[]    — which data sources contributed\n\nThis is the \"OSINT tab\" backend — the analyst sees everything in one call.\n\nAUTH: fail-closed via resolveSuiteCaller (admin / org partner key / business\nstaff session — the resolvePartnerOrAdmin chokepoint with allowBusinessSession,\nwhose admin path is the requireAdminOrSession trust model). The dossier is\nbuilt from GLOBAL threat-intel stores (enrichment / community intel / graph),\nthe same shared moat the other partner-reachable NEXUS surfaces expose — there\nare no per-tenant rows here to scope. The one operator-internal store, analyst\nNOTES, is returned to ADMINS ONLY (a partner/staff caller never sees them).\nGuard: nexus-operator-auth.\n\nRequest body:\n  { entity_type: string; entity_key: string; include_graph?: boolean; include_notes?: boolean }\n\nResponse 200:\n  { ok: true, entity_type, entity_key, enrichment, community, graph_summary, notes, indicators, risk_level, sources }",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/report": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Renders a server-side HTML investigation report for a NEXUS entity, suitable",
        "operationId": "get_api_nexus_v1_report",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "GET /api/nexus/v1/report  (AUTH-GATED, fail-closed)\n\nRenders a server-side HTML investigation report for a NEXUS entity, suitable\nfor printing to PDF. Accepts query parameters, fetches the entity panel from\n`buildEntityPanel()`, substitutes real data into the static HTML template\nlocated at `apps/web/app/nexus/report.html`, and returns `text/html`.\n\nQuery params:\n  address      — the wallet/contract address to report on (required)\n  chain        — chain key, e.g. \"eth\", \"btc\" (optional, cosmetic)\n  caseId       — investigator-supplied case reference (optional)\n  investigator — investigator email shown on cover + footer (optional)\n  notes        — free-text investigator notes (optional, URL-encoded)\n  fanout       — graph neighbour limit 1–500 (optional, default 200)\n\nAUTH — same fail-closed posture as every other NEXUS route: an admin on\nALLOWED_ADMIN_EMAILS (CF Access JWT / service token / signed session cookie),\nOR a valid partner key, OR a verified business-suite session.\nGuard: nexus-operator-auth.\n\nOn missing address: 404 with a clean error page.\nOn entity data failure: honest 503 error page, never a fake report.\nPOST/PUT/DELETE → 405."
      }
    },
    "/api/nexus/v1/sar": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Generates a FinCEN-format SAR draft from a NEXUS Dossier",
        "operationId": "post_api_nexus_v1_sar",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/sar\n\nGenerates a FinCEN-format SAR draft from a NEXUS Dossier.\nCloudflare Access-gated (compliance officer / authorised investigator).\n\nBody: SARDossierInput (see services/nexus/narrative/sar.ts)\nReturns: SARDraft with verifier verdict + hash signature.\n\nThrows 422 if the verifier rejects the draft as ungrounded - that's a\nSAFETY feature, not a bug: we refuse to emit a SAR draft the model\ncouldn't ground in evidence.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/search": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Universal intelligence search",
        "operationId": "get_api_nexus_v1_search",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/search   (AUTH-GATED, fail-closed)\nGET  /api/nexus/v1/search?q=...\n\nThe APEX/NEXUS UNIVERSAL INTELLIGENT SEARCH front-door. Given any query it\nDETECTS the type (card BIN / IBAN / wallet / domain / email / phone / ip /\nhash / ENS / company / person) and DISPATCHES to the right resolver/enricher,\nreturning ONE premium 360° envelope:\n\n  { query_redacted, detected_type, type_confidence, detection_reason,\n    alternative_types[], primary { …fields each {value, source, confidence} },\n    enrichment?, related_entities[], risk { score, flags, human_review_required,\n    advisory? }, provenance[], relevance_note, needs_paid_key }\n\nAUTH — investigative endpoint, FAILS CLOSED (via resolvePartnerOrAdmin). Either:\n  • an admin/operator on ALLOWED_ADMIN_EMAILS (via requireAdminOrSession: a\n    Cloudflare-Access JWT / service token / signed dashboard session cookie), OR\n  • a valid partner API key (Authorization: Bearer vlx_partner_<key>).\nA missing / unconfigured allowlist + no valid partner key ⇒ 403 (never opens).\nMirrors the assess/SAR posture + the CLAUDE.md \"fail CLOSED on auth\" rule.\n\nPII — the request query may contain a pasted PAN or IBAN; the orchestrator\nREDACTS it immediately (card→BIN, IBAN→structure, email-local masked) and only\nthe redacted form is returned/audited/logged. The full PAN / IBAN account is\nnever stored or echoed.\n\nGET supports a quick `?q=` for the dashboard; POST takes a JSON body. The\naudit row (redacted-only) is best-effort and never blocks the response."
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Universal intelligence search (structured body)",
        "operationId": "post_api_nexus_v1_search",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/search   (AUTH-GATED, fail-closed)\nGET  /api/nexus/v1/search?q=...\n\nThe APEX/NEXUS UNIVERSAL INTELLIGENT SEARCH front-door. Given any query it\nDETECTS the type (card BIN / IBAN / wallet / domain / email / phone / ip /\nhash / ENS / company / person) and DISPATCHES to the right resolver/enricher,\nreturning ONE premium 360° envelope:\n\n  { query_redacted, detected_type, type_confidence, detection_reason,\n    alternative_types[], primary { …fields each {value, source, confidence} },\n    enrichment?, related_entities[], risk { score, flags, human_review_required,\n    advisory? }, provenance[], relevance_note, needs_paid_key }\n\nAUTH — investigative endpoint, FAILS CLOSED (via resolvePartnerOrAdmin). Either:\n  • an admin/operator on ALLOWED_ADMIN_EMAILS (via requireAdminOrSession: a\n    Cloudflare-Access JWT / service token / signed dashboard session cookie), OR\n  • a valid partner API key (Authorization: Bearer vlx_partner_<key>).\nA missing / unconfigured allowlist + no valid partner key ⇒ 403 (never opens).\nMirrors the assess/SAR posture + the CLAUDE.md \"fail CLOSED on auth\" rule.\n\nPII — the request query may contain a pasted PAN or IBAN; the orchestrator\nREDACTS it immediately (card→BIN, IBAN→structure, email-local masked) and only\nthe redacted form is returned/audited/logged. The full PAN / IBAN account is\nnever stored or echoed.\n\nGET supports a quick `?q=` for the dashboard; POST takes a JSON body. The\naudit row (redacted-only) is best-effort and never blocks the response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/tagging": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Current tags or the version lineage for an entity",
        "operationId": "get_api_nexus_v1_tagging",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/nexus/v1/tagging  (AUTH-GATED, fail-closed)\n\n  GET  ?object_type=&object_key=        → the CURRENT tags on an object.\n  GET  ?object_type=&object_key=&tag=&source=&history=1\n                                         → the full version history (lineage).\n  POST { object_type, object_key, tag, source?, confidence?, evidence?,\n         category?, requires_review? }    → append a new (versioned) tag.\n  POST { confirm_tag_id, reviewer? }      → confirm a high-stakes tag (human review).\n\nTags are APPEND-ONLY + VERSIONED + PROVENANCE-STAMPED — never overwritten. The\nauthenticated operator is recorded as the `source` (POST add) / `reviewer`\n(POST confirm) unless a more specific source is supplied. High-stakes tags\nstay advisory until confirmed.\n\nAUTH — FAILS CLOSED via `requireAdminOrSession` (an operator on the allowlist:\nCF-Access JWT / service token / signed dashboard session cookie)."
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Tag an entity (versioned)",
        "operationId": "post_api_nexus_v1_tagging",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "/api/nexus/v1/tagging  (AUTH-GATED, fail-closed)\n\n  GET  ?object_type=&object_key=        → the CURRENT tags on an object.\n  GET  ?object_type=&object_key=&tag=&source=&history=1\n                                         → the full version history (lineage).\n  POST { object_type, object_key, tag, source?, confidence?, evidence?,\n         category?, requires_review? }    → append a new (versioned) tag.\n  POST { confirm_tag_id, reviewer? }      → confirm a high-stakes tag (human review).\n\nTags are APPEND-ONLY + VERSIONED + PROVENANCE-STAMPED — never overwritten. The\nauthenticated operator is recorded as the `source` (POST add) / `reviewer`\n(POST confirm) unless a more specific source is supplied. High-stakes tags\nstay advisory until confirmed.\n\nAUTH — FAILS CLOSED via `requireAdminOrSession` (an operator on the allowlist:\nCF-Access JWT / service token / signed dashboard session cookie).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/trace": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS Trace-Funds endpoint",
        "operationId": "post_api_nexus_v1_trace",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/trace  (AUTH-GATED, fail-closed)\n\nThe NEXUS Trace-Funds endpoint. Given a start (an address or a tx) on a chain\nand a direction, it follows the VALUE through the transfer graph and returns\nthe FlowTrace envelope: paths / terminals / predicted_destination /\nobfuscation_points / cross_chain / evidence / confidence / truncations.\n\nThe PROD SEAM: in production a node/indexer adapter answers the engine's\nper-address transfer queries live (an Esplora/Electrum BTC indexer, an EVM\nJSON-RPC + log indexer, a Solana RPC). A Cloudflare Worker cannot reach a\nnode, so THIS endpoint accepts the already-fetched neighbourhood in one of two\nshapes (documented in docs/nexus/TRACE_FUNDS.md):\n  • `transfers`: a pre-projected normalized Transfer[] (chain-agnostic), or\n  • `txs`: per-chain native txs (UtxoTx[] / EvmTx[] / SolanaTx[]) the endpoint\n    PROJECTS into Transfers via chains/{btc,evm,solana}.ts.\nEither way the engine walks an InMemoryTransferSource — identical logic to the\nlive path, which simply swaps in an indexer-backed TransferSource.\n\nAUTH — investigative SUITE endpoint → FAILS CLOSED via `resolvePartnerOrAdmin`\n(an operator on the allowlist: CF-Access JWT / service token / signed dashboard\nsession; OR a `vlx_live_` partner key; OR a business-suite STAFF session, which\nresolves server-side to the org's own tenant `partner:<business_id>`). This is\nthe same gate /search + the rest of the NEXUS suite use, so business staff can\ntrace from the NexusWorkspace, not just operators.\n\nSAFETY — every output carries confidence + evidence; a high-stakes\nclassification (drainer / scam / sanctioned) sets `requires_human_review`.\nDecision-support, never an automated accusation.\n\nGET returns 405; only POST is supported.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/transaction-analysis": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Runs the NEXUS 2.0 transaction forensics on a normalized UTXO tx",
        "operationId": "post_api_nexus_v1_transaction_analysis",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/transaction-analysis  (AUTH-GATED, fail-closed)\n\nRuns the NEXUS 2.0 transaction forensics on a normalized UTXO tx:\nchange-detection + multisig classification + behavioural tx-tags/fingerprint.\nReturns each engine's explainable envelope (result + confidence + evidence +\nexplanation).\n\nBody: { tx: UtxoTx, cluster_linked_indices?: number[] }\n\nAUTH — investigative endpoint, FAILS CLOSED via `requireAdminOrSession` (an\noperator on the allowlist: CF-Access JWT / service token / signed dashboard\nsession cookie; a missing/unconfigured allowlist denies).\n\nSAFETY — the multisig/ownership signals + the tags are OBSERVATIONS, never an\nidentity verdict. Heuristics carry their confidence; nothing is asserted as\nfact. GET → 405.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/workspace": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS INVESTIGATION-WORKSPACE spine",
        "operationId": "post_api_nexus_v1_workspace",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/workspace   (AUTH-GATED, fail-closed)\n\nThe NEXUS INVESTIGATION-WORKSPACE spine — ONE front-door for an analyst's\n\"open an investigation on X\". Given a workspace KIND + a REF it DISPATCHES to\nthe existing NEXUS engines (NEXUS exposure + graph for wallet/transaction;\nenrichment dossier + universal-search for entity/company/domain/email/phone;\nadvisory framing for person/social/fraud_case) and returns ONE uniform\n`InvestigationResult` envelope:\n\n  { kind, ref, summary, risk:{score,band,reasons[],confidence}, exposures[],\n    relationships[], sources[], coverage, confidence, human_review_required,\n    advisory? }\n\nBody: { kind, ref }\n  kind ∈ wallet | entity | person | domain | email | phone | company |\n         social | transaction | fraud_case\n  ref  = the entity reference (address / name / domain / email / handle).\n\nAUTH — investigative endpoint, FAILS CLOSED (via resolvePartnerOrAdmin, the\nSAME posture as /api/nexus/v1/search). Either:\n  • an admin/operator on ALLOWED_ADMIN_EMAILS (Cloudflare-Access JWT / service\n    token / signed dashboard session), OR\n  • a valid partner API key (Authorization: Bearer vlx_…).\nA missing / unconfigured allowlist + no valid partner key ⇒ 403 (never opens).\nOperator/business ONLY — never the CF-Access-only requireAdmin (the\nnexus-operator-auth guard). Mirrors the CLAUDE.md \"fail CLOSED on auth\" rule.\n\nSAFETY — the spine routes person / social / fraud_case through the\nclampActionForReview legitimacy chokepoint: a person can never carry an\nauto-block band, human_review_required is set, and the framing is\nassessment-not-accusation. Never-throws: resolveWorkspace is fully best-effort,\nand this handler wraps it so a dependency hiccup degrades to a 200 envelope\n(or a clean 4xx for a bad request), never a 500-storm.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/copilot/investigate": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "The NEXUS AI INVESTIGATOR COPILOT front-door",
        "operationId": "post_api_nexus_v1_copilot_investigate",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/copilot/investigate   (AUTH-GATED, fail-closed)\n\nThe NEXUS AI INVESTIGATOR COPILOT front-door — the \"one search → everything\"\nbounded agent. Given ONE reference it runs a BOUNDED plan over a typed registry\nof read-only tools (KYT screen / fund-trace / exposure-graph neighbours / entity\ndossier / universal 360 search), gathers GROUNDED evidence, then a SINGLE\ncost-guarded LLM synthesis (deterministic fallback) that summarises + suggests\nnext steps + flags anomalies + lists investigative gaps.\n\n  POST { ref: string, mode?: \"manual\" | \"auto\", chain?: \"btc\"|\"eth\"|\"sol\" }\n    → { ok, result: CopilotResult, requested_by, via }\n\n  • manual (default) — DRY-RUN: returns the plan an analyst would run + a cheap\n    grounded brief. Decision-support for an analyst who wants to drive.\n  • auto — runs the bounded plan (≤ 6 read-only tool calls) and synthesises.\n\nAUTH — investigative endpoint, FAILS CLOSED (via resolvePartnerOrAdmin). Either\nan admin/operator on ALLOWED_ADMIN_EMAILS (requireAdminOrSession: a CF-Access\nJWT / service token / signed dashboard session cookie) OR a valid partner key.\nA missing/unconfigured allowlist + no valid partner key ⇒ 403 (never opens) —\nthe same posture as /search + /kyt/screen.\n\nNEVER-THROWS — the agent degrades any tool failure to a recorded gap, and the\ndeterministic synthesis floor always produces a result. LLM-SKIPPABLE: the\nsingle LLM call is cost-guarded with a deterministic fallback, so a deployment\nwith no provider configured still returns a full, grounded brief.\n\nSAFETY (CLAUDE.md doctrine):\n  • LEGITIMACY — every step is analytical/risk/attribution over PUBLIC + lawful\n    data (the engines the console already uses); never deanonymisation.\n  • THE CLAMP — a PERSON / high-stakes subject is human_review_required and the\n  ",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cluster/assert": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Analyst-driven manual cluster attribution",
        "operationId": "post_api_nexus_v1_cluster_assert",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "description": "POST /api/nexus/v1/cluster/assert  (AUTH-GATED, fail-closed)\n\nAnalyst-driven manual cluster attribution: asserts that two wallet addresses\nbelong to the same controlling entity. Unlike the UTXO-heuristic clustering\nendpoint (`/api/nexus/v1/clustering`), this takes a direct analyst assertion\nand writes a `same_owner` edge at confidence 1.0 into graph_store.\n\nUse-cases:\n  • Manual: analyst recognises shared deposit/withdrawal pattern not caught by\n    heuristics (e.g. off-chain intelligence, CEX records, court documents).\n  • AI-suggested: the deep-investigation dossier flags high-confidence (≥ 0.85)\n    ownership overlap; the analyst clicks \"Confirm merge\" in the NEXUS panel.\n\nConservative by construction — only operators with a valid session can assert.\nThe edge carries `source = \"analyst_assertion\"` and the requester's identity\nso every materialised link is traceable. Idempotent: re-asserting the same\npair just refreshes the weight to 1.0.\n\nAUTH — operator-only, fails closed via `requireAdminOrSession` (CF-Access JWT /\nservice token / signed operator session cookie).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/alerts": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "List the tenant's alerts",
        "operationId": "get_api_nexus_v1_alerts",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/nexus/v1/alerts/{id}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "One alert",
        "operationId": "get_api_nexus_v1_alerts_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/alerts/{id}/triage": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Triage an alert",
        "operationId": "post_api_nexus_v1_alerts_id_triage",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "List the tenant's cases",
        "operationId": "get_api_nexus_v1_cases",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Create a case",
        "operationId": "post_api_nexus_v1_cases",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Case detail bundle",
        "operationId": "get_api_nexus_v1_cases_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "patch": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Update a case",
        "operationId": "patch_api_nexus_v1_cases_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}/entities": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Attach an entity",
        "operationId": "post_api_nexus_v1_cases_id_entities",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}/evidence": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Capture immutable evidence",
        "operationId": "post_api_nexus_v1_cases_id_evidence",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Evidence timeline",
        "operationId": "get_api_nexus_v1_cases_id_evidence",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/cases/{id}/notes": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Add an encrypted note",
        "operationId": "post_api_nexus_v1_cases_id_notes",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}/findings": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Record or version a finding",
        "operationId": "post_api_nexus_v1_cases_id_findings",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Current findings or lineage",
        "operationId": "get_api_nexus_v1_cases_id_findings",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/cases/{id}/bookmarks": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Pin an entity, graph state or search",
        "operationId": "post_api_nexus_v1_cases_id_bookmarks",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}/graphs": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Save a graph state",
        "operationId": "post_api_nexus_v1_cases_id_graphs",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/cases/{id}/reports": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Generate a versioned report",
        "operationId": "post_api_nexus_v1_cases_id_reports",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Current reports or lineage",
        "operationId": "get_api_nexus_v1_cases_id_reports",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/cases/{id}/reports/{rid}/status": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Advance report review",
        "operationId": "post_api_nexus_v1_cases_id_reports_rid_status",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/community": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Community posts for an entity",
        "operationId": "get_api_nexus_v1_community",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Create a post (stored pending)",
        "operationId": "post_api_nexus_v1_community",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/community/{id}/vote": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Vote on a post",
        "operationId": "post_api_nexus_v1_community_id_vote",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/community/{id}/moderate": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Moderate a post (operator only)",
        "operationId": "post_api_nexus_v1_community_id_moderate",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/notes": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "List notes for an entity",
        "operationId": "get_api_nexus_v1_notes",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Create a note",
        "operationId": "post_api_nexus_v1_notes",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/notes/{id}": {
      "put": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Edit a note",
        "operationId": "put_api_nexus_v1_notes_id",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Soft-delete a note",
        "operationId": "delete_api_nexus_v1_notes_id",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/watchlist": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "List watches",
        "operationId": "get_api_nexus_v1_watchlist",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Add a watch (idempotent per target)",
        "operationId": "post_api_nexus_v1_watchlist",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/watchlist/{id}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "One watch",
        "operationId": "get_api_nexus_v1_watchlist_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Remove a watch",
        "operationId": "delete_api_nexus_v1_watchlist_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v1/watchlist/{id}/status": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Pause or resume a watch",
        "operationId": "post_api_nexus_v1_watchlist_id_status",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v1/watchlist/{id}/rescreen": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Re-screen now",
        "operationId": "post_api_nexus_v1_watchlist_id_rescreen",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/prism/workbooks": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "List workbooks",
        "operationId": "get_api_nexus_prism_workbooks",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Create a workbook",
        "operationId": "post_api_nexus_prism_workbooks",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/prism/workbooks/{id}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Get a workbook",
        "operationId": "get_api_nexus_prism_workbooks_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "put": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Update a workbook",
        "operationId": "put_api_nexus_prism_workbooks_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Delete a workbook",
        "operationId": "delete_api_nexus_prism_workbooks_id",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/prism/workbooks/{id}/share": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Create a share token",
        "operationId": "post_api_nexus_prism_workbooks_id_share",
        "security": [
          {
            "operatorSession": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/prism/shared/{token}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Fetch a shared workbook",
        "operationId": "get_api_nexus_prism_shared_token",
        "security": [],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v2/conclude": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Run the predictive engine and store a conclusion",
        "operationId": "post_api_nexus_v2_conclude",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/nexus/v2/conclusion/{id}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Fetch a stored conclusion",
        "operationId": "get_api_nexus_v2_conclusion_id",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v2/subject/{kind}/{value}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Conclusions for a subject",
        "operationId": "get_api_nexus_v2_subject_kind_value",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "value",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v2/signals/{kind}/{value}": {
      "get": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Signals for a subject",
        "operationId": "get_api_nexus_v2_signals_kind_value",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "value",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/nexus/v2/feedback": {
      "post": {
        "tags": [
          "NEXUS"
        ],
        "summary": "Operator feedback on a conclusion",
        "operationId": "post_api_nexus_v2_feedback",
        "security": [
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success envelope `{ ok: true, ... }`."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "vlx_live_<key>",
        "description": "Partner API key. Fails closed: a missing, inactive, under-scoped or over-quota key is rejected (never silently downgraded)."
      },
      "operatorSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "vilkax_sid",
        "description": "Signed operator / business session (or a Cloudflare Access JWT on Access-fronted hosts), checked by requireAdminOrSession / resolveTenantScope. Fails closed; ALLOWED_ADMIN_EMAILS-checked for operators."
      }
    },
    "headers": {
      "BilledUnits": {
        "description": "Units this call consumed.",
        "schema": {
          "type": "integer"
        }
      },
      "ResponseTime": {
        "description": "Server compute time, e.g. `12ms`.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotentReplayed": {
        "description": "`true` when this response was replayed from a stored result rather than executed. Absent when no Idempotency-Key was sent, and also absent (rather than `false`) if the replay store was unreachable — so a caller can distinguish 'first execution' from 'we could not guarantee it'.",
        "schema": {
          "type": "boolean"
        }
      },
      "RateLimitLimit": {
        "description": "Your tier's per-minute burst ceiling. Emitted on 2xx so you can pace BEFORE being throttled.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Metered units left this calendar month. Absent on an unmetered tier — absent, never a fabricated number.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "Verdict": {
        "type": "string",
        "enum": [
          "allow",
          "review",
          "block"
        ]
      },
      "ObservedSignal": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "A known taxonomy signal id. Unknown ids are dropped."
          },
          "value": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1,
            "description": "Signal strength."
          },
          "note": {
            "type": "string",
            "description": "Optional note. Redacted + capped server-side."
          }
        }
      },
      "DecisionContext": {
        "type": "object",
        "properties": {
          "action_kind": {
            "type": "string",
            "enum": [
              "browse",
              "signin",
              "transfer",
              "install",
              "message",
              "unknown"
            ]
          },
          "history": {
            "type": "object",
            "properties": {
              "known": {
                "type": "boolean"
              },
              "riskyActionsSeen": {
                "type": "integer"
              },
              "trust": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              }
            }
          },
          "answers": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "enum": [
                "yes",
                "no",
                "unsure"
              ]
            }
          }
        }
      },
      "DecideRequest": {
        "type": "object",
        "properties": {
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ObservedSignal"
            },
            "maxItems": 64
          },
          "text": {
            "type": "string",
            "maxLength": 4000,
            "description": "Optional indicator text — redacted + classified server-side, never stored."
          },
          "osint": {
            "type": "object",
            "properties": {
              "risk": {
                "type": "number"
              },
              "knownBad": {
                "type": "boolean"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "url",
                  "subject"
                ]
              }
            }
          },
          "context": {
            "$ref": "#/components/schemas/DecisionContext"
          }
        }
      },
      "DecideResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "partner": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          },
          "billed_units": {
            "type": "integer"
          },
          "decision": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "typeLabel": {
                "type": "string"
              },
              "severity": {
                "type": "string"
              },
              "confidence": {
                "type": "number"
              },
              "action": {
                "$ref": "#/components/schemas/Verdict"
              },
              "score": {
                "type": "number"
              },
              "why": {
                "type": "string"
              },
              "questions": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "combinations": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "contributing": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "humanReviewRequired": {
                "type": "boolean"
              },
              "engine": {
                "type": "string"
              }
            }
          }
        }
      },
      "BusinessDecideRequest": {
        "type": "object",
        "properties": {
          "features": {
            "type": "array",
            "maxItems": 128,
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Opaque B2B signal id — NEVER raw PII."
                },
                "value": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1,
                  "default": 1
                }
              }
            }
          },
          "subject_ref": {
            "type": "string",
            "maxLength": 128,
            "description": "Opaque/hashed subject id — no raw PII."
          },
          "subject_kind": {
            "type": "string",
            "enum": [
              "transaction",
              "account",
              "onboarding",
              "merchant",
              "entity",
              "unknown"
            ]
          },
          "context": {
            "type": "object"
          }
        }
      },
      "BusinessDecideResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "partner": {
            "type": "string"
          },
          "capability_tier": {
            "type": "string"
          },
          "billed_units": {
            "type": "integer"
          },
          "gated_features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "decision": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "typeLabel": {
                "type": "string"
              },
              "tier": {
                "type": "string"
              },
              "confidence": {
                "type": "number"
              },
              "action": {
                "$ref": "#/components/schemas/Verdict"
              },
              "score": {
                "type": "number"
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "why": {
                "type": "string"
              },
              "alternativeExplanations": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "humanReviewRequired": {
                "type": "boolean"
              },
              "engine": {
                "type": "string"
              }
            }
          }
        }
      },
      "SignalLookupResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "partner": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email",
              "url",
              "entity"
            ]
          },
          "query": {
            "type": "string"
          },
          "degraded": {
            "type": "boolean",
            "description": "True when a source wasn't configured — the result is still well-formed, never a false clear."
          },
          "billed_units": {
            "type": "integer"
          },
          "signal": {
            "type": "object",
            "description": "Shape varies by type (reputation/verdict/categories/reasons…)."
          }
        }
      },
      "SriResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "partner": {
            "type": "string"
          },
          "capability_tier": {
            "type": "string"
          },
          "billed_units": {
            "type": "integer"
          },
          "entity": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "hash": {
                "type": "string",
                "description": "The entity is identified only by its hash + a defanged preview."
              },
              "preview": {
                "type": "string"
              }
            }
          },
          "score": {
            "type": "number"
          },
          "tier": {
            "type": "string"
          },
          "confidence": {
            "type": "number"
          },
          "breakdown": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "evidence": {
            "type": "object"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "KytScreenRequest": {
        "type": "object",
        "description": "Supply exactly one target. A wallet/tx target requires `chain` (btc|eth|sol); a counterparty requires `counterparty_type`.",
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "btc",
              "eth",
              "sol"
            ]
          },
          "address": {
            "type": "string"
          },
          "tx": {
            "type": "string"
          },
          "counterparty": {
            "type": "string"
          },
          "counterparty_type": {
            "type": "string"
          },
          "transfers": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Optional pre-projected transfers."
          }
        }
      },
      "KytScreenResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "billed_units": {
            "type": "integer"
          },
          "via": {
            "type": "string",
            "enum": [
              "partner",
              "admin"
            ]
          },
          "screening": {
            "type": "object",
            "properties": {
              "verdict": {
                "$ref": "#/components/schemas/Verdict"
              },
              "confidence": {
                "type": "number"
              },
              "risk_score": {
                "type": "number"
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "evidence": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "coverage": {
                "type": "string",
                "description": "full | partial — a partial-coverage clean result degrades to review."
              },
              "human_review_required": {
                "type": "boolean"
              },
              "advisory": {
                "type": "string"
              }
            }
          }
        }
      },
      "UsageResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "partner": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          },
          "period": {
            "type": "string",
            "description": "YYYY-MM"
          },
          "quota": {
            "type": "object",
            "properties": {
              "monthly_quota_units": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "used_units": {
                "type": "integer"
              },
              "remaining_units": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "rate_limit_rpm": {
                "type": "integer"
              }
            }
          },
          "total_calls": {
            "type": "integer"
          },
          "by_endpoint": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "endpoint": {
                  "type": "string"
                },
                "units": {
                  "type": "integer"
                },
                "calls": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Stable machine-readable error code."
          },
          "detail": {
            "type": "string"
          }
        },
        "required": [
          "ok",
          "error"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Endpoint id (uuidv4)."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute HTTPS URL on a public host."
          },
          "events": {
            "type": "string",
            "description": "Comma-separated event filter, or `*` for all.",
            "example": "decision.review,threat.screened"
          },
          "enabled": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "enabled"
        ]
      },
      "WebhookEndpointCreated": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "endpoint": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "secret": {
            "type": "string",
            "description": "The HMAC-SHA256 signing secret. Returned EXACTLY ONCE, on create and on rotate. There is no read-back path: a lost secret is rotated, not recovered.",
            "example": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
          },
          "secret_notice": {
            "type": "string"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "One delivery ATTEMPT. Metadata only — the body we sent is not retained.",
        "properties": {
          "delivery_id": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          },
          "event_type": {
            "type": "string",
            "example": "decision.review"
          },
          "status_code": {
            "type": "integer",
            "nullable": true,
            "description": "HTTP status your endpoint returned; null on a network failure."
          },
          "attempt": {
            "type": "integer",
            "description": "Attempts made (1 = first try succeeded; 2 = one retry)."
          },
          "latency_ms": {
            "type": "integer",
            "nullable": true,
            "description": "Wall-clock across ALL attempts. `null` — never 0 — when it was not measured."
          },
          "endpoint_id": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ]
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AuditEvent": {
        "type": "object",
        "description": "One audit row, flattened. Nested objects are awkward in a SIEM, so payloads stay as JSON strings.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "business_id": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string"
          },
          "domain": {
            "type": "string",
            "example": "b2b"
          },
          "event_type": {
            "type": "string",
            "example": "business.webhook_endpoint_created"
          },
          "severity": {
            "type": "string"
          },
          "actor_kind": {
            "type": "string"
          },
          "actor_id": {
            "type": "string",
            "nullable": true
          },
          "subject_kind": {
            "type": "string",
            "nullable": true
          },
          "subject_user_id": {
            "type": "string",
            "nullable": true
          },
          "surface": {
            "type": "string",
            "nullable": true
          },
          "meta_json": {
            "type": "string",
            "nullable": true,
            "description": "PII pseudonymised at write time."
          },
          "before_json": {
            "type": "string",
            "nullable": true
          },
          "after_json": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string",
            "nullable": true
          },
          "row_hash": {
            "type": "string",
            "nullable": true
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed body or parameters (e.g. `bad_body`, `no_signals`, `pii_in_features`, `invalid_type`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing/invalid key (`invalid_auth`, `invalid_api_key`).",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but not permitted (`insufficient_scope`, `tier_gated`, `key_revoked`, `partner_suspended`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BodyTooLarge": {
        "description": "Request body exceeded the per-endpoint limit (`body_too_large`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Burst rate limit or monthly quota exhausted (`rate_limited`, `quota_exceeded`).",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-limit": {
            "schema": {
              "type": "integer"
            }
          },
          "x-ratelimit-remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "x-quota-limit": {
            "schema": {
              "type": "integer"
            }
          },
          "x-quota-used": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Wrong HTTP method (`method_not_allowed`).",
        "headers": {
          "Allow": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Backing store briefly unavailable (`db_not_bound`). Retry — never a false success.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error (`screen_failed`, `decision_failed`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "A request with this Idempotency-Key is still in flight (`idempotency_in_progress`). Retry after the advertised delay.",
        "headers": {
          "retry-after": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "IdempotencyKeyReuse": {
        "description": "This Idempotency-Key was already used for a request with a DIFFERENT body (`idempotency_key_reuse`). Generate a new key per distinct operation — we refuse rather than answer the first question.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255,
          "example": "8f14e45f-ceea-467a-9ec2-8e1e1a1a1a1a"
        },
        "description": "Opt-in safe retries. Send a unique value (a UUID is the usual choice) and a repeat of the SAME request returns the FIRST response byte-for-byte instead of executing again — no second review case, no second webhook, no second billed call. Scoped to your tenant and this endpoint; records expire after 24 hours, after which the key is reusable. Re-using a key with a DIFFERENT body is refused (422) rather than answered from the cache. Omit the header for the legacy at-least-once behaviour."
      }
    }
  }
}
