{
  "openapi": "3.1.0",
  "info": {
    "title": "Vilkax Protect API",
    "version": "1.0.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.",
    "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." }
  ],
  "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.0 }
                ],
                "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" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DecideResponse" } } }
          },
          "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/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" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BusinessDecideResponse" } } }
          },
          "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/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/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" }
        }
      }
    }
  },
  "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)."
      }
    },
    "headers": {
      "BilledUnits": { "description": "Units this call consumed.", "schema": { "type": "integer" } },
      "ResponseTime": { "description": "Server compute time, e.g. `12ms`.", "schema": { "type": "string" } }
    },
    "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"]
      }
    },
    "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" } } }
      }
    }
  }
}
