{
  "openapi": "3.1.0",
  "info": {
    "title": "xrplriskscore.ai API",
    "version": "1.0.0",
    "description": "AI-powered XRPL wallet risk scoring. All paid endpoints use the x402 protocol — send XRP on XRPL Mainnet, get a risk verdict back. No API keys, no subscriptions.",
    "contact": {
      "name": "xrplriskscore.ai",
      "email": "contact@xrplriskscore.ai",
      "url": "https://xrplriskscore.ai"
    },
    "license": {
      "name": "Commercial",
      "url": "https://xrplriskscore.ai"
    }
  },
  "servers": [
    { "url": "https://xrplriskscore.ai", "description": "Production (XRPL Mainnet)" }
  ],
  "components": {
    "securitySchemes": {
      "x402-xrpl": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PAYMENT",
        "description": "x402 payment protocol over XRPL. Step 1: make an unauthenticated GET — the server returns HTTP 402 with a challenge body containing payTo, amount (in drops), and an invoiceId. Step 2: build an XRPL Payment tx sending the required drops to payTo with the invoiceId in a Memo, sign it with your wallet, base64-encode {x402Version, accepted, payload:{signedTxBlob}} and retry with that value in the X-PAYMENT header. The PAYMENT-SIGNATURE header is also accepted for backward compatibility."
      }
    },
    "schemas": {
      "WalletAddress": {
        "type": "string",
        "pattern": "^r[1-9A-HJ-NP-Za-km-z]{24,34}$",
        "example": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
        "description": "A valid XRPL r-address"
      },
      "Verdict": {
        "type": "string",
        "enum": ["ALLOW", "CHALLENGE", "BLOCK"],
        "description": "ALLOW = no significant risk signals. CHALLENGE = elevated risk, verify before transacting. BLOCK = serious risk signals, do not send XRP."
      },
      "Confidence": {
        "type": "string",
        "enum": ["HIGH", "MEDIUM", "LOW"]
      },
      "RiskLevel": {
        "type": "string",
        "enum": ["LOW", "MEDIUM", "HIGH"]
      },
      "VerdictDetail": {
        "type": "string",
        "enum": ["LOW_RISK", "ELEVATED", "MODERATE", "HIGH_CONCERN", "HIGH_RISK", "CRITICAL"]
      },
      "ComplianceVerdict": {
        "type": "string",
        "enum": ["COMPLIANT", "REVIEW", "NON_COMPLIANT"]
      },
      "CredentialVerdict": {
        "type": "string",
        "enum": ["CREDENTIAL_READY", "REVIEW_REQUIRED", "CREDENTIAL_DENIED"]
      },
      "EscrowVerdict": {
        "type": "string",
        "enum": ["ESCROW_SAFE", "ESCROW_REVIEW", "ESCROW_DENIED"]
      },
      "Disclaimer": {
        "type": "string",
        "description": "Legal disclaimer — for informational purposes only, not financial or legal advice"
      },
      "X402Challenge": {
        "type": "object",
        "description": "HTTP 402 response body. Build an XRPL Payment tx from accepts[0] and retry with X-PAYMENT header.",
        "required": ["x402Version", "error", "accepts", "instructions"],
        "properties": {
          "x402Version": { "type": "integer", "example": 2 },
          "error": { "type": "string", "example": "Payment required" },
          "accepts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "scheme":            { "type": "string", "example": "exact" },
                "network":           { "type": "string", "example": "xrpl:0" },
                "asset":             { "type": "string", "example": "XRP" },
                "payTo":             { "type": "string", "example": "rU7kCg3PrDGXtKocUpEvpy6xiTgvsKLHPG" },
                "amount":            { "type": "string", "description": "Amount in drops (1 XRP = 1000000)", "example": "1000000" },
                "maxTimeoutSeconds": { "type": "integer", "example": 600 },
                "extra": {
                  "type": "object",
                  "properties": {
                    "invoiceId":  { "type": "string", "format": "uuid", "description": "UUID that MUST appear in the tx Memo (replay protection)" },
                    "sourceTag":  { "type": "integer", "example": 804681468 }
                  }
                },
                "resource":     { "type": "string" },
                "description":  { "type": "string" }
              }
            }
          },
          "instructions": {
            "type": "object",
            "properties": {
              "summary": { "type": "string" },
              "step1":   { "type": "string" },
              "step2":   { "type": "string" },
              "step3":   { "type": "string" },
              "step4":   { "type": "string" },
              "step5":   { "type": "string" }
            }
          }
        }
      },
      "ScoreBreakdown": {
        "type": "object",
        "description": "Full 31-signal breakdown from the /score endpoint",
        "properties": {
          "accountAgeDays":           { "type": "integer" },
          "totalTransactions":        { "type": "integer" },
          "last24hrTransactions":     { "type": "integer" },
          "balanceXRP":               { "type": "number" },
          "spendableXRP":             { "type": "number" },
          "hasTrustlines":            { "type": "boolean" },
          "hasDomain":                { "type": "boolean" },
          "hasPaymentChannels":       { "type": "boolean" },
          "uniqueDestinations":       { "type": "integer" },
          "offerCancelRatio":         { "type": "number" },
          "highVelocityFlag":         { "type": "boolean" },
          "lowDiversityFlag":         { "type": "boolean" },
          "highCancelRatioFlag":      { "type": "boolean" },
          "washTradingPairs":         { "type": "integer" },
          "washTradingFlag":          { "type": "boolean" },
          "fanOutFlag":               { "type": "boolean" },
          "dormancyFlag":             { "type": "boolean" },
          "roundNumberRatio":         { "type": "number" },
          "roundNumberFlag":          { "type": "boolean" },
          "botTimingFlag":            { "type": "boolean" },
          "avgTimeBetweenTxSeconds":  { "type": "number" },
          "rapidOutflowFlag":         { "type": "boolean" },
          "sanctionsCheck":           { "type": "boolean" },
          "isSanctioned":             { "type": "boolean" },
          "sanctionSource":           { "type": ["string", "null"] },
          "isKnownScam":              { "type": "boolean" },
          "scamLabel":                { "type": ["string", "null"] },
          "scamCategory":             { "type": ["string", "null"] },
          "txAnalyzed":               { "type": "integer" },
          "totalTxOnLedger":          { "type": "integer" },
          "walletType":               { "type": "string", "enum": ["RETAIL", "ACTIVE", "HIGH_VOLUME", "INSTITUTIONAL"] }
        }
      },
      "ScoreResponse": {
        "type": "object",
        "required": ["schema_version", "wallet", "verdict", "riskScore"],
        "properties": {
          "schema_version": { "type": "string", "example": "1.0", "description": "Response schema version — increment when breaking changes are made" },
          "wallet":        { "$ref": "#/components/schemas/WalletAddress" },
          "verdict":       { "$ref": "#/components/schemas/Verdict" },
          "verdictDetail": { "$ref": "#/components/schemas/VerdictDetail" },
          "confidence":    { "$ref": "#/components/schemas/Confidence" },
          "riskScore":     { "type": "integer", "minimum": 0, "maximum": 100 },
          "riskLevel":     { "$ref": "#/components/schemas/RiskLevel" },
          "tags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Behavioral tags derived from on-chain signals. Risk tags appear first, followed by neutral and positive tags.",
            "example": ["established_wallet", "trustline_holder", "large_balance"]
          },
          "breakdown":     { "$ref": "#/components/schemas/ScoreBreakdown" },
          "flags":         { "type": "array", "items": { "type": "string" } },
          "summary":       { "type": "string" },
          "sub_scores":    { "$ref": "#/components/schemas/SubScores", "description": "v2 only — multi-dimensional risk breakdown. Present when ?v=2 is passed." },
          "graph":         { "$ref": "#/components/schemas/GraphAnalysis", "description": "v2 only — network graph analysis" },
          "_disclaimer":   { "$ref": "#/components/schemas/Disclaimer" },
          "_scoreNote":    { "type": "string" }
        }
      },
      "GraphAnalysis": {
        "type": "object",
        "description": "Network-level graph signals derived from transaction counterparty analysis. v2 only.",
        "properties": {
          "cluster_size":          { "type": "integer", "description": "Number of unique counterparty addresses in transaction history" },
          "inflow_concentration":  { "type": "number",  "description": "Share of total inflow XRP from the single largest sender (0.0-1.0)" },
          "outflow_concentration": { "type": "number",  "description": "Share of total outflow XRP to the single largest recipient (0.0-1.0)" },
          "flagged_counterparties":{ "type": "integer", "description": "Number of direct counterparties appearing on sanctions or scam lists" },
          "hops_to_flagged":       { "type": ["integer", "null"], "description": "Minimum hops to a flagged wallet. 1 = direct contact. null = none detected." },
          "graph_risk_score":      { "type": "integer", "minimum": 0, "maximum": 100, "description": "Composite graph risk score 0-100" }
        }
      },
      "SubScores": {
        "type": "object",
        "description": "Multi-dimensional risk breakdown. Each dimension scores 0-100 independently of the overall riskScore.",
        "properties": {
          "velocity_risk":     { "type": "integer", "minimum": 0, "maximum": 100, "description": "Transaction speed and automation risk" },
          "counterparty_risk": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Quality and diversity of transaction counterparties" },
          "pattern_risk":      { "type": "integer", "minimum": 0, "maximum": 100, "description": "Suspicious behavioral patterns in transaction history" },
          "compliance_risk":   { "type": "integer", "minimum": 0, "maximum": 100, "description": "Regulatory and known-bad-actor exposure" }
        }
      },
      "PrescoreResponse": {
        "type": "object",
        "required": ["schema_version", "wallet", "verdict", "riskScore", "quickCheck"],
        "properties": {
          "schema_version": { "type": "string", "example": "1.0" },
          "wallet":     { "$ref": "#/components/schemas/WalletAddress" },
          "verdict":    { "$ref": "#/components/schemas/Verdict" },
          "confidence": { "type": "string", "enum": ["LOW"], "description": "Always LOW — only 3 signals checked" },
          "riskScore":  { "type": "integer", "minimum": 0, "maximum": 100 },
          "quickCheck": { "type": "boolean", "example": true },
          "signals": {
            "type": "object",
            "properties": {
              "accountExists":  { "type": "boolean" },
              "accountAgeDays": { "type": "integer" },
              "balanceXRP":     { "type": "number" },
              "isNewAccount":   { "type": "boolean" }
            }
          },
          "note":        { "type": "string" },
          "_disclaimer": { "$ref": "#/components/schemas/Disclaimer" }
        }
      },
      "RwaCheckResponse": {
        "type": "object",
        "required": ["schema_version", "wallet", "complianceScore", "complianceVerdict"],
        "properties": {
          "schema_version":   { "type": "string", "example": "2.0" },
          "wallet":           { "$ref": "#/components/schemas/WalletAddress" },
          "complianceScore":  { "type": "integer", "minimum": 0, "maximum": 100 },
          "complianceVerdict": { "$ref": "#/components/schemas/ComplianceVerdict" },
          "rwaActivity": {
            "type": "object",
            "properties": {
              "holdsRwaTokens":      { "type": "boolean" },
              "rwaHoldings":         { "type": "array", "items": { "type": "object" } },
              "holdsRlusd":          { "type": "boolean" },
              "rlusdBalance":        { "type": "number" },
              "rwaTransactionCount": { "type": "integer" },
              "uniqueRwaIssuers":    { "type": "integer" },
              "escrowActivity":      { "type": "boolean" },
              "openRwaOffers":       { "type": "integer" }
            }
          },
          "complianceSignals": {
            "type": "object",
            "properties": {
              "accountAgeDays":    { "type": "integer" },
              "hasDomain":         { "type": "boolean" },
              "hasTrustlines":     { "type": "boolean" },
              "trustlineCount":    { "type": "integer" },
              "ownerCount":        { "type": "integer" },
              "balanceXRP":        { "type": "number" },
              "hasEscrowActivity": { "type": "boolean" },
              "isNewAccount":      { "type": "boolean" }
            }
          },
          "flags":       { "type": "array", "items": { "type": "string" } },
          "summary":     { "type": "string" },
          "risk_signals_considered": { "type": "array", "items": { "type": "string" }, "description": "Signal names (with severity suffix where applicable) that fired during risk-signal layering. Examples: \"sanctions_hit\", \"scam_db_hit\", \"wash_trading_mild\", \"nft_offer_dump_severe\", \"memo_phishing_mild\". Empty array if none fired." },
          "note":        { "type": "string" },
          "_disclaimer": { "$ref": "#/components/schemas/Disclaimer" }
        }
      },
      "CredentialCheckResponse": {
        "type": "object",
        "required": ["schema_version", "wallet", "verdict", "credentialScore"],
        "properties": {
          "schema_version":  { "type": "string", "example": "1.0" },
          "wallet":          { "$ref": "#/components/schemas/WalletAddress" },
          "verdict":         { "$ref": "#/components/schemas/CredentialVerdict" },
          "credentialScore": { "type": "integer", "minimum": 0, "maximum": 100 },
          "recommendation":  { "type": "string" },
          "checks": {
            "type": "object",
            "properties": {
              "sanctionsClean":    { "type": "boolean" },
              "scamDatabaseClean": { "type": "boolean" },
              "accountMaturity":   { "type": "string", "enum": ["NEW", "DEVELOPING", "ESTABLISHED", "MATURE"] },
              "behaviorClean":     { "type": "boolean" },
              "tokenReady":        { "type": "boolean" },
              "domainVerified":    { "type": "boolean" },
              "walletType":        { "type": "string" }
            }
          },
          "flags":          { "type": "array", "items": { "type": "string" } },
          "sanctionsCheck": { "type": "boolean" },
          "_disclaimer":    { "$ref": "#/components/schemas/Disclaimer" },
          "timestamp":      { "type": "string", "format": "date-time" }
        }
      },
      "EscrowCheckResponse": {
        "type": "object",
        "required": ["schema_version", "wallet", "verdict", "escrowRiskScore"],
        "properties": {
          "schema_version":  { "type": "string", "example": "1.0" },
          "wallet":          { "$ref": "#/components/schemas/WalletAddress" },
          "verdict":         { "$ref": "#/components/schemas/EscrowVerdict" },
          "escrowRiskScore": { "type": "integer", "minimum": 0, "maximum": 100 },
          "scoreDriver":     { "type": "string" },
          "recommendation":  { "type": "string" },
          "escrowProfile": {
            "type": "object",
            "properties": {
              "escrowsCreated":       { "type": "integer" },
              "escrowsFinished":      { "type": "integer" },
              "escrowsCancelled":     { "type": "integer" },
              "escrowCompletionRate": { "type": ["number", "null"] },
              "activeEscrowObjects":  { "type": "integer" },
              "hasTokenEscrows":      { "type": "boolean" },
              "recentEscrowActivity": { "type": "boolean" },
              "tokenEscrowReady":     { "type": "boolean" },
              "walletType":           { "type": "string" },
              "accountMaturity":      { "type": "string" }
            }
          },
          "riskSignals": {
            "type": "object",
            "properties": {
              "sanctionsClean":    { "type": "boolean" },
              "scamDatabaseClean": { "type": "boolean" },
              "behaviorClean":     { "type": "boolean" },
              "hasDomain":         { "type": "boolean" },
              "hasPaymentChannels":{ "type": "boolean" },
              "hasTrustlines":     { "type": "boolean" }
            }
          },
          "flags":          { "type": "array", "items": { "type": "string" } },
          "sanctionsCheck": { "type": "boolean" },
          "_disclaimer":    { "$ref": "#/components/schemas/Disclaimer" },
          "timestamp":      { "type": "string", "format": "date-time" }
        }
      },
      "BatchScoreResponse": {
        "type": "object",
        "required": ["results", "tier", "walletsScored", "pricePaidXRP", "perWalletXRP"],
        "properties": {
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ScoreResponse" },
            "description": "One ScoreResponse per wallet in the same order as the request"
          },
          "tier":          { "type": "integer", "enum": [1, 2, 3], "description": "1 = 1-10 wallets (8 XRP), 2 = 11-25 wallets (20 XRP), 3 = 26-50 wallets (40 XRP)" },
          "walletsScored": { "type": "integer", "description": "Number of wallets processed" },
          "pricePaidXRP":  { "type": "number", "description": "Total XRP charged for this batch (8, 20, or 40)" },
          "perWalletXRP":  { "type": "number", "enum": [0.8], "description": "Effective per-wallet cost (0.8 XRP = 20% off single /score)" },
          "priceXRP":      { "type": "number", "description": "Alias for pricePaidXRP — kept for backwards compatibility" }
        }
      },
      "BatchJobAccepted": {
        "type": "object",
        "description": "Returned immediately (HTTP 202) when a webhookUrl is provided. Scoring runs in the background; results are POSTed to the webhook on completion.",
        "required": ["jobId", "status", "tier", "walletsQueued", "priceXRP", "statusUrl"],
        "properties": {
          "jobId":         { "type": "string", "format": "uuid" },
          "status":        { "type": "string", "enum": ["running"], "example": "running" },
          "tier":          { "type": "integer", "enum": [1, 2, 3] },
          "walletsQueued": { "type": "integer" },
          "priceXRP":      { "type": "number" },
          "statusUrl":     { "type": "string", "format": "uri", "description": "Poll this URL to check job progress" }
        }
      },
      "BatchJobStatus": {
        "type": "object",
        "description": "Response from GET /score-batch/{jobId}",
        "required": ["status", "tier", "walletsQueued", "priceXRP"],
        "properties": {
          "status":        { "type": "string", "enum": ["running", "complete", "error"] },
          "tier":          { "type": "integer", "enum": [1, 2, 3] },
          "walletsQueued": { "type": "integer" },
          "priceXRP":      { "type": "number" },
          "createdAt":     { "type": "integer", "description": "Unix ms timestamp" },
          "completedAt":   { "type": ["integer", "null"] },
          "deliveredAt":   { "type": ["integer", "null"], "description": "Null if webhook delivery is still pending" },
          "error":         { "type": ["string", "null"] },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ScoreResponse" },
            "description": "Only present when status = complete"
          }
        }
      },
      "DemoResponse": {
        "type": "object",
        "description": "Verdict-only response from demo endpoints. Full analysis requires payment.",
        "required": ["wallet", "verdict", "_demo"],
        "properties": {
          "wallet":   { "$ref": "#/components/schemas/WalletAddress" },
          "verdict":  { "type": "string", "description": "The verdict string. For /demo/score and /demo/prescore: ALLOW|CHALLENGE|BLOCK. For /demo/rwa-check: COMPLIANT|REVIEW|NON_COMPLIANT. For /demo/credential-check: CREDENTIAL_READY|REVIEW_REQUIRED|CREDENTIAL_DENIED. For /demo/escrow-check: ESCROW_SAFE|ESCROW_REVIEW|ESCROW_DENIED." },
          "_demo":    { "type": "boolean", "example": true },
          "_note":    { "type": "string" },
          "_upgrade": { "type": "string", "example": "https://xrplriskscore.ai/#connect" }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status":             { "type": "string", "example": "ok" },
          "service":            { "type": "string", "example": "xrplriskscore.ai" },
          "version":            { "type": "string", "example": "1.0" },
          "xrplConnected":      { "type": "boolean" },
          "lastScoreTimestamp": { "type": "string" },
          "endpoints":          { "type": "object" },
          "network":            { "type": "string", "example": "xrpl:0 (mainnet)" },
          "facilitator":        { "type": "string" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        }
      },
      "RateLimitResponse": {
        "type": "object",
        "properties": {
          "error":   { "type": "string", "example": "Free demo limit reached" },
          "limit":   { "type": "string", "example": "3 free checks per 24 hours" },
          "upgrade": { "type": "object" },
          "_demo":   { "type": "boolean" }
        }
      }
    },
    "parameters": {
      "walletAddress": {
        "name": "walletAddress",
        "in": "path",
        "required": true,
        "schema": { "$ref": "#/components/schemas/WalletAddress" },
        "description": "Any valid XRPL wallet address starting with r"
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": { "type": "string", "format": "uuid" },
        "description": "Optional idempotency key (recommend a UUID). If the same wallet + key combination is seen again within 10 minutes, the server returns the cached response with X-Idempotency-Replayed: true — without triggering another on-chain payment. Safe retry window for network drops and timeouts."
      }
    },
    "responses": {
      "402Challenge": {
        "description": "Payment required. No X-PAYMENT header was sent, or a previous payment was rejected. Parse the body, build the XRPL Payment tx, and retry.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/X402Challenge" }
          }
        }
      },
      "400BadWallet": {
        "description": "Invalid XRPL wallet address",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "404NotFound": {
        "description": "Wallet not found on XRPL mainnet",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "429RateLimit": {
        "description": "Demo rate limit reached (3 requests per IP per 24 hours)",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/RateLimitResponse" }
          }
        }
      },
      "500Error": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      }
    }
  },
  "paths": {
    "/score/{walletAddress}": {
      "get": {
        "operationId": "scoreWalletPaid",
        "summary": "Full 31-signal XRPL wallet risk score (paid, 1 XRP via x402)",
        "description": "Scores an XRPL wallet address for risk. Use to evaluate a counterparty before sending funds, or to self-assess your own wallet before initiating a transaction or signing as an AI agent.\n\nRuns a comprehensive 31-signal risk analysis including account age, transaction velocity, counterparty diversity, wash trading detection, fan-out patterns, dormancy spikes, bot timing, rapid outflow, domain verification, payment channels, trustlines, offer cancel ratio, sanctions screening, scam database lookup, NFT offer dump detection, memo phishing detection, trust line intelligence (frozen / scam-issuer / zero-balance counts), account flag analysis (defaultRipple / globalFreeze / requireDestTag / disallowXRP / noFreeze), behavioral patterns (7-day velocity, round-number bias, fan-out ratio, self-dealing), and account-object inspection (open offers, payment channels, NFT pages). Returns a 0-100 risk score and ALLOW/CHALLENGE/BLOCK verdict.\n\n**Price: 1 XRP (1,000,000 drops)**\n\nUses the x402 protocol: make an unauthenticated GET first to receive the 402 challenge, then retry with the X-PAYMENT header.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/walletAddress" },
          { "$ref": "#/components/parameters/idempotencyKey" },
          {
            "name": "v",
            "in": "query",
            "required": false,
            "description": "Response schema version. Pass 2 for v2 schema with risk_category, primary_risk_factors, and recommended_action fields. Default is v1.",
            "schema": { "type": "string", "enum": ["2"] }
          },
          {
            "name": "explain",
            "in": "query",
            "required": false,
            "description": "Pass `true` to include a plain-English narrative `explanation` paragraph and a `generated_at` ISO timestamp alongside the normal response body. Bundled at 1 XRP — no extra charge.",
            "schema": { "type": "string", "enum": ["true"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Risk analysis complete",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScoreResponse" },
                "examples": {
                  "v2": {
                    "summary": "v2 schema response (when ?v=2)",
                    "value": {
                      "schema_version": "2.0",
                      "score_version": "2.0.0",
                      "wallet": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
                      "verdict": "ALLOW",
                      "riskScore": 12,
                      "risk_category": "clean",
                      "sub_scores": {
                        "velocity_risk": 0,
                        "counterparty_risk": 0,
                        "pattern_risk": 0,
                        "compliance_risk": 0
                      },
                      "graph": {
                        "cluster_size": 87,
                        "inflow_concentration": 0.18,
                        "outflow_concentration": 0.22,
                        "flagged_counterparties": 0,
                        "hops_to_flagged": null,
                        "graph_risk_score": 0
                      },
                      "primary_risk_factors": [],
                      "mitigating_factors": ["Account is 2341 days old", "Verified domain field set"],
                      "recommended_action": {
                        "for_agents": "proceed",
                        "for_humans": "No significant risk signals detected. Standard due diligence still applies."
                      },
                      "signals_evaluated": 31,
                      "signals_triggered": 0,
                      "evaluated_at": "2026-04-22T18:00:00Z",
                      "cache_ttl_seconds": 300
                    }
                  }
                }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/prescore/{walletAddress}": {
      "get": {
        "operationId": "prescoreWalletPaid",
        "summary": "Fast 3-signal pre-check (paid, 0.1 XRP via x402)",
        "description": "Quick 3-second wallet sanity check using only account_info. Checks account age, balance, and existence. Use this before sending a large payment to quickly flag obvious risks without waiting for a full 31-signal analysis.\n\n**Price: 0.1 XRP (100,000 drops)**\n\nConfidence is always LOW. Use /score for full analysis.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/walletAddress" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "responses": {
          "200": {
            "description": "Pre-check complete",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PrescoreResponse" }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/rwa-check/{walletAddress}": {
      "get": {
        "operationId": "rwaCheckWalletPaid",
        "summary": "RWA compliance check (paid, 0.5 XRP via x402)",
        "description": "Checks a wallet's suitability for Real World Asset (RWA) interactions on XRPL. Returns RLUSD holdings, trustline configuration, RWA transaction history, escrow activity, and a 0-100 compliance score.\n\n**Price: 0.5 XRP (500,000 drops)**\n\nVerdicts: COMPLIANT / REVIEW / NON_COMPLIANT.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/walletAddress" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "responses": {
          "200": {
            "description": "RWA compliance check complete",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RwaCheckResponse" }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "404": { "$ref": "#/components/responses/404NotFound" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/credential-check/{walletAddress}": {
      "get": {
        "operationId": "credentialCheckWallet",
        "summary": "Permissioned Domain credential screening (0.5 XRP)",
        "description": "Screens a wallet for eligibility to receive an XRPL Permissioned Domain credential (XLS-80d). Runs sanctions screening, scam database check, behavioral analysis, and account maturity assessment.\n\n**Price: 0.5 XRP (500,000 drops)**\n\nVerdicts: CREDENTIAL_READY / REVIEW_REQUIRED / CREDENTIAL_DENIED.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/walletAddress" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "responses": {
          "200": {
            "description": "Credential screening complete",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CredentialCheckResponse" }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/escrow-check/{walletAddress}": {
      "get": {
        "operationId": "escrowCheckWallet",
        "summary": "XLS-85 escrow counterparty screening (0.5 XRP)",
        "description": "Screens a wallet's suitability as an escrow counterparty on XRPL. Analyzes escrow history (creation, completion rate, cancellations), token escrow readiness (XLS-85), behavioral signals, and sanctions/scam status.\n\n**Price: 0.5 XRP (500,000 drops)**\n\nVerdicts: ESCROW_SAFE / ESCROW_REVIEW / ESCROW_DENIED.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/walletAddress" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "responses": {
          "200": {
            "description": "Escrow counterparty check complete",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EscrowCheckResponse" }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "404": { "$ref": "#/components/responses/404NotFound" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/compliance-bundle": {
      "post": {
        "operationId": "complianceBundle",
        "summary": "Unified compliance bundle — score + RWA + credential in one paid call",
        "description": "Runs /score (full 31-signal risk), /rwa-check (RWA/RLUSD compliance), and /credential-check (XLS-80/81 eligibility) in parallel for a single wallet and returns a merged response with an overall_verdict (PASS | REVIEW | BLOCK). If any single check errors, the other two still return and the overall_verdict is at least REVIEW. Uses the x402 protocol: POST with {wallet} first (no X-PAYMENT) to receive the 402 challenge, then retry with the X-PAYMENT header.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["wallet"],
                "properties": {
                  "wallet": { "$ref": "#/components/schemas/WalletAddress" }
                }
              },
              "examples": {
                "basic": {
                  "summary": "Check a single wallet",
                  "value": { "wallet": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bundle complete — merged results from all three checks plus overall_verdict and summary counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["wallet", "timestamp", "overall_verdict", "risk_score", "rwa_compliance", "credential_eligibility", "summary"],
                  "properties": {
                    "wallet":                 { "$ref": "#/components/schemas/WalletAddress" },
                    "timestamp":              { "type": "string", "format": "date-time" },
                    "overall_verdict":        { "type": "string", "enum": ["PASS", "REVIEW", "BLOCK"] },
                    "risk_score":             { "type": "object", "description": "Full /score response, or { error: true, message } on failure" },
                    "rwa_compliance":         { "type": "object", "description": "Full /rwa-check response, or { error: true, message } on failure" },
                    "credential_eligibility": { "type": "object", "description": "Full /credential-check response, or { error: true, message } on failure" },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "checks_passed": { "type": "integer" },
                        "checks_failed": { "type": "integer" },
                        "checks_total":  { "type": "integer", "enum": [3] }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body — missing or malformed wallet address",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/provision-wallet": {
      "post": {
        "operationId": "provisionWallet",
        "summary": "Generate and fund a fresh XRPL wallet — 4 XRP",
        "description": "Generates a brand-new XRPL wallet keypair (address, seed, publicKey, privateKey) and transfers 2 XRP from the service wallet to the new address so it is active on mainnet immediately. The 4 XRP fee covers a service fee plus the 2 XRP funding transfer. Returns 503 if the service wallet balance is below the operational minimum (5 XRP).\n\n**SECURITY:** the response contains private key material (seed and privateKey). Call this endpoint over HTTPS only, and never log the response body. The seed is shown only once.\n\n**Uses the x402 protocol:** POST first without an X-PAYMENT header to receive the 402 challenge, then retry the same POST with the X-PAYMENT header.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "responses": {
          "200": {
            "description": "Fresh XRPL keypair generated and funded with 2 XRP from the service wallet. The new wallet is active on mainnet.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "wallet", "funding", "riskScore", "instructions"],
                  "properties": {
                    "success": { "type": "boolean" },
                    "wallet": {
                      "type": "object",
                      "required": ["address", "seed", "publicKey", "privateKey"],
                      "properties": {
                        "address":    { "type": "string", "description": "New XRPL r-address" },
                        "seed":       { "type": "string", "description": "Secret seed (sEd…). Shown only once — store securely." },
                        "publicKey":  { "type": "string", "description": "ED public key (hex)" },
                        "privateKey": { "type": "string", "description": "ED private key (hex). Shown only once." }
                      }
                    },
                    "funding": {
                      "type": "object",
                      "required": ["reserveMet", "amountSent", "txHash"],
                      "properties": {
                        "reserveMet": { "type": "boolean", "description": "True when the 2 XRP funding transfer settled on-ledger." },
                        "amountSent": { "type": "string",  "description": "Drops sent to the new address (\"2000000\" on success, \"0\" on failure)." },
                        "txHash":     { "type": ["string", "null"], "description": "XRPL transaction hash of the funding payment, if it settled." }
                      }
                    },
                    "riskScore": {
                      "type": "object",
                      "required": ["verdict", "score", "message"],
                      "properties": {
                        "verdict": { "type": "string", "enum": ["LOW_RISK"] },
                        "score":   { "type": "integer", "enum": [0] },
                        "message": { "type": "string" }
                      }
                    },
                    "instructions": {
                      "type": "object",
                      "required": ["warning", "nextStep"],
                      "properties": {
                        "warning":  { "type": "string" },
                        "nextStep": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "503": {
            "description": "Provisioning temporarily unavailable — service wallet balance below operational minimum.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error":   { "type": "string" },
                    "message": { "type": "string" }
                  }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/score-batch": {
      "post": {
        "operationId": "scoreBatch",
        "summary": "Batch risk score — up to 50 wallets (tiered pricing)",
        "description": "Score up to 50 wallets in a single x402 payment. Tiered pricing with a 20% bulk discount vs per-wallet rate:\n\n| Tier | Wallets | Price  | Effective/wallet |\n|------|---------|--------|------------------|\n| 1    | 1–10    | 8 XRP  | 0.8 XRP          |\n| 2    | 11–25   | 20 XRP | 0.8 XRP          |\n| 3    | 26–50   | 40 XRP | 0.8 XRP          |\n\nAll wallets are validated before the 402 challenge is issued — bad addresses are rejected for free. Wallets are scored in parallel (up to 10 concurrent).\n\n**Webhook mode:** Include a `webhookUrl` to get an immediate `202 Accepted` with a `jobId`. The server scores wallets in the background and POSTs results to your URL when done (3 attempts with 2s/4s backoff). Poll `GET /score-batch/{jobId}` to check status.\n\n**Uses the x402 protocol:** POST with the wallet list first (no X-PAYMENT) to receive the 402 challenge at the correct tier price, then retry the same POST with the X-PAYMENT header.",
        "tags": ["Paid Endpoints"],
        "security": [{ "x402-xrpl": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["wallets"],
                "properties": {
                  "wallets": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/WalletAddress" },
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "List of XRPL wallet addresses to score (1–50)"
                  },
                  "webhookUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional. If provided, scoring runs in the background and results are POSTed here on completion. The endpoint returns 202 with a jobId instead of waiting."
                  }
                }
              },
              "examples": {
                "synchronous": {
                  "summary": "Synchronous (wait for results)",
                  "value": { "wallets": ["rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "rN7n3473SaZBCG4dFL83w7PB5AMtFBQT3"] }
                },
                "async-webhook": {
                  "summary": "Async with webhook delivery",
                  "value": {
                    "wallets": ["rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "rN7n3473SaZBCG4dFL83w7PB5AMtFBQT3"],
                    "webhookUrl": "https://your-app.example.com/webhook/xrpl-results"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch scoring complete (synchronous — no webhookUrl provided)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchScoreResponse" }
              }
            }
          },
          "202": {
            "description": "Batch job queued (async — webhookUrl was provided). Results will be POSTed to the webhook URL.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchJobAccepted" }
              }
            }
          },
          "400": {
            "description": "Invalid request body — bad wallet addresses, wrong count, or invalid webhookUrl",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "402": { "$ref": "#/components/responses/402Challenge" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/score-batch/{jobId}": {
      "get": {
        "operationId": "getBatchJob",
        "summary": "Poll async batch job status",
        "description": "Check the status of a batch scoring job started with a `webhookUrl`. Returns `running` while in progress, `complete` with full results, or `error` if scoring failed. Jobs expire after 2 hours.",
        "tags": ["Paid Endpoints"],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "The jobId returned in the 202 response from POST /score-batch"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status (may be running, complete, or error)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchJobStatus" }
              }
            }
          },
          "404": {
            "description": "Job not found or expired (jobs live for 2 hours)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/demo/score/{walletAddress}": {
      "get": {
        "operationId": "scoreWallet",
        "summary": "Wallet risk score (verdict only, free demo)",
        "description": "Free demo of /score. Returns verdict only — no risk score, breakdown, flags, or reasoning. Rate-limited to 3 requests per IP per 24 hours.",
        "tags": ["Demo Endpoints (Free)"],
        "parameters": [{ "$ref": "#/components/parameters/walletAddress" }],
        "responses": {
          "200": {
            "description": "Verdict returned (stripped)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DemoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "429": { "$ref": "#/components/responses/429RateLimit" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/demo/prescore/{walletAddress}": {
      "get": {
        "operationId": "prescoreWallet",
        "summary": "Wallet pre-check (verdict only, free demo)",
        "description": "Free demo of /prescore. Returns verdict only. Rate-limited to 3 requests per IP per 24 hours.",
        "tags": ["Demo Endpoints (Free)"],
        "parameters": [{ "$ref": "#/components/parameters/walletAddress" }],
        "responses": {
          "200": {
            "description": "Verdict returned (stripped)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DemoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "429": { "$ref": "#/components/responses/429RateLimit" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/demo/rwa-check/{walletAddress}": {
      "get": {
        "operationId": "rwaCheckWallet",
        "summary": "RWA compliance check (verdict only, free demo)",
        "description": "Free demo of /rwa-check. Returns verdict (COMPLIANT/REVIEW/NON_COMPLIANT) only. Rate-limited to 3 requests per IP per 24 hours.",
        "tags": ["Demo Endpoints (Free)"],
        "parameters": [{ "$ref": "#/components/parameters/walletAddress" }],
        "responses": {
          "200": {
            "description": "Compliance verdict returned (stripped)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DemoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "429": { "$ref": "#/components/responses/429RateLimit" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/demo/credential-check/{walletAddress}": {
      "get": {
        "operationId": "demoCredentialCheckWallet",
        "summary": "Demo: credential screening (verdict only, free)",
        "description": "Free demo of /credential-check. Returns verdict (CREDENTIAL_READY/REVIEW_REQUIRED/CREDENTIAL_DENIED) only. Rate-limited to 3 requests per IP per 24 hours.",
        "tags": ["Demo Endpoints (Free)"],
        "parameters": [{ "$ref": "#/components/parameters/walletAddress" }],
        "responses": {
          "200": {
            "description": "Credential verdict returned (stripped)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DemoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "429": { "$ref": "#/components/responses/429RateLimit" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/demo/escrow-check/{walletAddress}": {
      "get": {
        "operationId": "demoEscrowCheckWallet",
        "summary": "Demo: escrow check (verdict only, free)",
        "description": "Free demo of /escrow-check. Returns verdict (ESCROW_SAFE/ESCROW_REVIEW/ESCROW_DENIED) only. Rate-limited to 3 requests per IP per 24 hours.",
        "tags": ["Demo Endpoints (Free)"],
        "parameters": [{ "$ref": "#/components/parameters/walletAddress" }],
        "responses": {
          "200": {
            "description": "Escrow verdict returned (stripped)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DemoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/400BadWallet" },
          "429": { "$ref": "#/components/responses/429RateLimit" },
          "500": { "$ref": "#/components/responses/500Error" }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Service health check",
        "description": "Returns service status, XRPL connectivity, and endpoint pricing summary. No authentication required.",
        "tags": ["Utility"],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Paid Endpoints",
      "description": "Require x402 XRP payment. Make an unauthenticated GET first to receive the 402 challenge, then retry with X-PAYMENT header."
    },
    {
      "name": "Demo Endpoints (Free)",
      "description": "Free, verdict-only responses. Rate-limited to 3 requests per IP per 24 hours. No payment required."
    },
    {
      "name": "Utility",
      "description": "Health, discovery, and documentation endpoints."
    }
  ]
}
