{"openapi":"3.0.3","info":{"title":"VikingIntel API","version":"1.0.0","description":"Solana wallet intelligence and forensics API. Generated from apps/web/src/app/api's route inventory — see this file's top comment for what is and isn't fully schema-documented yet."},"servers":[{"url":"/api","description":"Same origin as the dashboard"}],"tags":[{"name":"System"},{"name":"Async Jobs"},{"name":"Reports"},{"name":"Sybil"},{"name":"Cases"},{"name":"Protocols"},{"name":"Watchlist"},{"name":"Wallet"},{"name":"Token"},{"name":"Token Intelligence"},{"name":"Graph"},{"name":"Account"},{"name":"Alerts"},{"name":"Billing"},{"name":"Copilot"},{"name":"Explorer"},{"name":"Transaction Intelligence"},{"name":"Fund Flow"},{"name":"Intelligence"},{"name":"Known Destinations"},{"name":"Private Labels"},{"name":"Protocol Monitoring"},{"name":"Sybil Detection"},{"name":"Trace My Funds"},{"name":"Tracer"},{"name":"Bulk Triage"},{"name":"Quick Screen"},{"name":"Wallet Intelligence"},{"name":"Verification"},{"name":"API Keys"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"Bearer <api key>"},"sessionCookie":{"type":"apiKey","in":"cookie","name":"sb-access-token","description":"Dashboard session (Supabase auth cookie) — the browser sets this automatically; not usable from external API clients."},"webhookSecret":{"type":"apiKey","in":"header","name":"x-helius-webhook-secret","description":"Configured on the Helius webhook registration, not a per-caller credential."}},"schemas":{"CaseSummary":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["OPEN","CLOSED"]},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"closedAt":{"type":"string","format":"date-time","nullable":true}}},"WatchedWallet":{"type":"object","properties":{"address":{"type":"string"},"label":{"type":"string","nullable":true},"notifyWebhookUrl":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"MonitoredAsset":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"assetType":{"type":"string","enum":["TREASURY","MULTISIG","ADMIN","DEPLOYER","PROGRAM","LIQUIDITY","COUNTERPARTY"]},"label":{"type":"string","nullable":true},"criticality":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"status":{"type":"string","enum":["ACTIVE","PAUSED"]},"notes":{"type":"string","nullable":true},"enabledDetections":{"type":"array","items":{"type":"string"}},"addedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}},"paths":{"/api/health":{"get":{"operationId":"getApiHealth","summary":"Liveness check","tags":["System"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Returns HTTP 503 (not 200) when the DB check fails — status/db reflect that in the body too, so a caller inspecting only the JSON still sees it.","properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"db":{"type":"string","enum":["connected","error"]},"latencyMs":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/check":{"post":{"operationId":"postApiSybilCheck","summary":"Enqueue a snapshot sybil-clustering job","description":"Requires DEVELOPER tier or above.","tags":["Async Jobs"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["addresses"],"properties":{"addresses":{"type":"array","items":{"type":"string"},"description":"Base58 wallet addresses; batch cap depends on tier (100 DEVELOPER, 500 GROWTH/INTERNAL)"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["PENDING"]}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/jobs/{id}":{"get":{"operationId":"getApiSybilJobsId","summary":"Poll a sybil job","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Sybil Detection"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/jobs":{"get":{"operationId":"getApiSybilJobs","summary":"List sybil check jobs","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Sybil Detection"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/jobs/{id}/report":{"get":{"operationId":"getApiSybilJobsIdReport","summary":"Screening report for a completed job (json/csv/pdf via ?format=)","tags":["Reports"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Presentation layer only over the same poll-route data, no new detection. A non-COMPLETE job returns the same status-only shape as GET /api/sybil/jobs/{id}, so a client can poll this one endpoint throughout the job lifecycle. csv gives one row per flagged address.","properties":{"data":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","running","complete","failed"]},"submitted":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true},"error":{"type":"string","description":"Present only when status is failed."},"flaggedCount":{"type":"integer","description":"Present only when status is complete."},"flaggedPct":{"type":"number"},"clusterCount":{"type":"integer"},"knownFarmersCount":{"type":"integer"},"smartMoneyDampenedCount":{"type":"integer"},"bands":{"type":"object","description":"Cluster counts bucketed by confidence band."},"decisions":{"type":"object","properties":{"approved":{"type":"integer"},"rejected":{"type":"integer"},"needsReview":{"type":"integer"},"undecided":{"type":"integer"}}},"clusters":{"type":"array","description":"Sorted by confidence descending — this ordering defines clusterIndex, which the CSV format references, so json and csv agree on cluster numbering.","items":{"type":"object","properties":{"clusterId":{"type":"string"},"confidence":{"type":"number"},"band":{"type":"string"},"bandLabel":{"type":"string"},"signals":{"type":"array","items":{"type":"string"}},"memberCount":{"type":"integer"},"sharedFunder":{"type":"string","nullable":true},"decision":{"type":"string","nullable":true},"decisionReason":{"type":"string","nullable":true}}}},"summary":{"type":"string","description":"Human-readable, paste-into-a-thread summary sentence."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/jobs/export":{"get":{"operationId":"getApiSybilJobsExport","summary":"Bulk export of the caller's recent sybil jobs (json/csv/pdf via ?format=) — one zipped PDF per job for pdf","tags":["Reports"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"\"Show me every job I've run\" (one summary row per job, up to 500) — not \"show me everything about job X,\" which is what GET /api/sybil/jobs/{id}/report already covers. format=pdf silently skips any non-COMPLETE job (nothing to render for a pending/failed screening).","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"walletCount":{"type":"integer"},"clusterCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true}}}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"truncated":{"type":"boolean"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/clusters/{clusterId}/decision":{"post":{"operationId":"postApiSybilClustersClusterIdDecision","summary":"Record an analyst decision on a cluster","tags":["Sybil"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"clusterId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"description":"Append-only — always a new ClusterDecision row, never an edit; a changed mind is a new decision. Ownership-scoped through the parent job (a cluster has no owner of its own).","properties":{"decision":{"type":"string","enum":["APPROVED","REJECTED","NEEDS_REVIEW"]},"reason":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"clusterId":{"type":"string"},"decision":{"type":"string"},"reason":{"type":"string","nullable":true},"reviewedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/clusters/{clusterId}/note":{"post":{"operationId":"postApiSybilClustersClusterIdNote","summary":"Add a note to a cluster","tags":["Sybil"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"clusterId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":5000}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"clusterId":{"type":"string"},"body":{"type":"string"},"authorId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/clusters/{clusterId}/history":{"get":{"operationId":"getApiSybilClustersClusterIdHistory","summary":"Cluster membership history","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Sybil Detection"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"clusterId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/consolidation/scan":{"post":{"operationId":"postApiConsolidationScan","summary":"Enqueue a consolidation-sink scan","description":"Requires DEVELOPER tier or above.","tags":["Async Jobs"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["addresses"],"description":"Trace-forward: given known claimant wallets from a past airdrop, finds the sink addresses they swept funds into. Same enqueue+poll contract as /api/sybil/check — poll via GET /api/consolidation/jobs/{id}. Batch cap depends on tier (0 FREE — rejected before this check even runs, 100 DEVELOPER, 500 GROWTH/INTERNAL).","properties":{"addresses":{"type":"array","items":{"type":"string"},"description":"Base58 claimant wallet addresses."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending"]},"poll":{"type":"string"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/consolidation/jobs/{id}":{"get":{"operationId":"getApiConsolidationJobsId","summary":"Poll a consolidation scan job","tags":["Async Jobs"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Shape depends on status: PENDING/RUNNING returns just the base fields; FAILED adds `error`; COMPLETE spreads the full scan result (claimantsScanned/sinks/unresolved) alongside the base fields.","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending","running","complete","failed"]},"claimants":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true},"error":{"type":"string","description":"Present only when status is failed."},"claimantsScanned":{"type":"integer","description":"Present only when status is complete."},"sinks":{"type":"array","description":"Present only when status is complete.","items":{"type":"object","properties":{"address":{"type":"string"},"inflowCount":{"type":"integer","description":"Distinct input claimants that sent funds here."},"totalSol":{"type":"number"},"fromClaimants":{"type":"array","items":{"type":"string"}}}}},"unresolved":{"type":"array","items":{"type":"string"},"description":"Present only when status is complete — claimants whose history fetch returned nothing."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/fundflow/trace":{"post":{"operationId":"postApiFundflowTrace","summary":"Enqueue a fund-flow trace from an address","description":"Requires DEVELOPER tier or above.","tags":["Async Jobs"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"sinceTimestamp":{"type":"integer","description":"Unix seconds; optional lower bound"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Enqueue-only, same submit/poll contract as /api/sybil/check — the worker runs the actual hop-by-hop trace. 429 (not enqueued) past 5 concurrent jobs per requester. Poll via GET /api/fundflow/jobs/{id}.","properties":{"data":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["pending"]},"poll":{"type":"string"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/fundflow/jobs/{id}":{"get":{"operationId":"getApiFundflowJobsId","summary":"Poll a fund-flow trace job (includes queuedAhead position)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Fund Flow"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases":{"get":{"operationId":"getApiCases","summary":"List investigations owned by the caller","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiCases","summary":"Create a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":5000},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["OPEN","CLOSED"]},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}":{"get":{"operationId":"getApiCasesId","summary":"Case detail with items and counts","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"patch":{"operationId":"patchApiCasesId","summary":"Close or reopen a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"description":"Deliberately narrow — status is the only mutable field on a case; title/description edits go through a note instead, keeping the append-only history readable rather than silently rewriting what a case was originally about.","properties":{"status":{"type":"string","enum":["OPEN","CLOSED"]}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["OPEN","CLOSED"]}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/items":{"post":{"operationId":"postApiCasesIdItems","summary":"Add a wallet/cluster/token to a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemType","subject"],"description":"A CLUSTER subject must be one this requester already owns (checked through the cluster's parent sybil job) — otherwise a case could silently reference another account's private screening output.","properties":{"itemType":{"type":"string","enum":["WALLET","CLUSTER","TOKEN"],"description":"Type of item to add: WALLET (Solana base58 address), CLUSTER (sybil cluster cuid), or TOKEN (token mint address)."},"subject":{"type":"string","maxLength":64,"description":"Base58 address for WALLET/TOKEN, cuid for CLUSTER."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"itemType":{"type":"string"},"subject":{"type":"string"},"addedBy":{"type":"string"},"addedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiCasesIdItems","summary":"Remove a subject item from the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/notes":{"post":{"operationId":"postApiCasesIdNotes","summary":"Add a note to a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":5000,"description":"Note content — use this field (not \"content\") for case notes."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"caseId":{"type":"string"},"body":{"type":"string"},"authorId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdNotes","summary":"Analyst notes on the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/decisions":{"post":{"operationId":"postApiCasesIdDecisions","summary":"Record a case-level decision","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"description":"Append-only, same convention as a cluster decision — a changed mind is a new row, never an edit; latest by createdAt wins.","properties":{"decision":{"type":"string","enum":["APPROVED","REJECTED","NEEDS_REVIEW"],"description":"Decision outcome. Append-only: a changed mind creates a new row."},"reason":{"type":"string","maxLength":2000,"description":"Reviewer rationale (max 2000 chars). Also accepts \"rationale\" as an alias."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"caseId":{"type":"string"},"decision":{"type":"string"},"reason":{"type":"string","nullable":true},"reviewedBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdDecisions","summary":"Case decision history (append-only)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/findings":{"post":{"operationId":"postApiCasesIdFindings","summary":"Manually add a finding","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdFindings","summary":"Findings recorded on the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/tags":{"put":{"operationId":"putApiCasesIdTags","summary":"Replace a case's tags","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tags"],"description":"Full replace, not append/remove — send the complete desired tag set every call.","properties":{"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/timeline":{"get":{"operationId":"getApiCasesIdTimeline","summary":"Chronological case timeline with source-health meta","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/report":{"get":{"operationId":"getApiCasesIdReport","summary":"Rendered case report (md/csv/pdf); PRO-gated formats return 403 on FREE","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/export":{"get":{"operationId":"getApiCasesExport","summary":"Export all cases as JSON","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/pins":{"post":{"operationId":"postApiCasesIdPins","summary":"Pin a wallet/cluster/note/decision to a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pinType","subject"],"description":"Idempotent upsert on (caseId, pinType, subject) — pinning the same item twice doesn't create a duplicate.","properties":{"pinType":{"type":"string","enum":["WALLET","CLUSTER","TOKEN","NOTE","DECISION"]},"subject":{"type":"string","maxLength":64,"description":"Base58 address for WALLET/TOKEN, cuid for CLUSTER/NOTE/DECISION."},"label":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"pinType":{"type":"string"},"subject":{"type":"string"},"label":{"type":"string","nullable":true},"pinnedBy":{"type":"string"},"pinnedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdPins","summary":"Pinned subjects","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiCasesIdPins","summary":"Unpin a subject","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/saved-queries":{"post":{"operationId":"postApiCasesIdSaved-queries","summary":"Save a filter/search configuration on a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","queryType","params"],"description":"params is opaque JSON (fund-flow trace params, entity graph query, etc.) — this route never inspects or validates its shape beyond it being a JSON object; interpreting it is the caller's responsibility.","properties":{"name":{"type":"string","maxLength":200},"queryType":{"type":"string","maxLength":100},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"queryType":{"type":"string"},"params":{"type":"object"},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdSaved-queries","summary":"Saved queries for the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiCasesIdSaved-queries","summary":"Delete a saved query","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/graph-layouts":{"put":{"operationId":"putApiCasesIdGraph-layouts","summary":"Save (or replace, by name) a graph layout on a case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","layout"],"description":"PUT, not POST — save-under-a-name is an upsert (same name replaces the prior save), not a strictly new resource each call. Persists node positions, zoom, and visible relationship types so returning to a case restores the layout an analyst arranged.","properties":{"name":{"type":"string","maxLength":200},"layout":{"type":"object"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"layout":{"type":"object"},"createdBy":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdGraph-layouts","summary":"Saved graph layouts","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiCasesIdGraph-layouts","summary":"Delete a saved graph layout","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/snapshots":{"post":{"operationId":"postApiCasesIdSnapshots","summary":"Freeze a point-in-time snapshot of the case","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Freezes a denormalized copy of the case's current status/tags/items/notes/decisions at write time — not a live query — for comparing \"what did we know when\" against the case's live state later.","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","nullable":true},"takenBy":{"type":"string"},"takenAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdSnapshots","summary":"Snapshot metadata list","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/snapshots/{snapshotId}":{"get":{"operationId":"getApiCasesIdSnapshotsSnapshotId","summary":"Full snapshot body","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/evidence":{"post":{"operationId":"postApiCasesIdEvidence","summary":"Upload evidence to a case (multipart/form-data)","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"multipart/form-data, not JSON: `file` (required, max 25MB), `kind` (required — ATTACHMENT, SCREENSHOT, or PDF_BUNDLE), `notes` (optional). A SHA-256 chain-of-custody hash is computed server-side over the exact bytes received at ingest time and stored alongside the file.","properties":{"file":{"type":"string","format":"binary"},"kind":{"type":"string","enum":["ATTACHMENT","SCREENSHOT","PDF_BUNDLE"]},"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"filename":{"type":"string"},"mimeType":{"type":"string"},"sizeBytes":{"type":"integer"},"sha256":{"type":"string"},"notes":{"type":"string","nullable":true},"uploadedBy":{"type":"string"},"uploadedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"get":{"operationId":"getApiCasesIdEvidence","summary":"List evidence attached to a case (metadata only)","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Metadata only — file content is never included in the list; fetch GET /api/cases/{id}/evidence/{evidenceId} to download.","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"filename":{"type":"string"},"mimeType":{"type":"string"},"sizeBytes":{"type":"integer"},"sha256":{"type":"string"},"notes":{"type":"string","nullable":true},"uploadedBy":{"type":"string"},"uploadedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/evidence/{evidenceId}":{"get":{"operationId":"getApiCasesIdEvidenceEvidenceId","summary":"Evidence metadata or download redirect","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"evidenceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiCasesIdEvidenceEvidenceId","summary":"Soft-delete an evidence attachment","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"evidenceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/evidence/{evidenceId}/hold":{"post":{"operationId":"postApiCasesIdEvidenceEvidenceIdHold","summary":"Place a legal hold on evidence — DELETE then retains the R2 object instead of deleting it (idempotent; re-hold preserves the original hold)","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"evidenceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"held":{"type":"boolean","enum":[true]},"alreadyHeld":{"type":"boolean"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/protocols":{"get":{"operationId":"getApiProtocols","summary":"Monitored protocols","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiProtocols","summary":"Register a new protocol","tags":["Protocols"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":120},"slug":{"type":"string","description":"Derived from name when omitted."},"description":{"type":"string","maxLength":2000},"website":{"type":"string","format":"uri"},"minSeverity":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"notifyWebhookUrl":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/protocols/{id}":{"get":{"operationId":"getApiProtocolsId","summary":"Protocol detail with monitored assets","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"patch":{"operationId":"patchApiProtocolsId","summary":"Update protocol metadata, status, or monitoring config","tags":["Protocols"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"description":{"type":"string","maxLength":2000,"nullable":true},"website":{"type":"string","format":"uri","nullable":true},"status":{"type":"string","enum":["ACTIVE","PAUSED","ARCHIVED"]},"minSeverity":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"notifyWebhookUrl":{"type":"string","format":"uri","nullable":true}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiProtocolsId","summary":"Remove a protocol","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/protocols/{id}/assets":{"get":{"operationId":"getApiProtocolsIdAssets","summary":"Assets monitored for a protocol","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiProtocolsIdAssets","summary":"Add a monitored asset","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/protocols/{id}/assets/{assetId}":{"patch":{"operationId":"patchApiProtocolsIdAssetsAssetId","summary":"Update a monitored asset (label, criticality, pause/resume, notes)","tags":["Protocols"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":100,"nullable":true},"criticality":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"status":{"type":"string","enum":["ACTIVE","PAUSED"]},"notes":{"type":"string","maxLength":2000,"nullable":true}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","nullable":true},"criticality":{"type":"string"},"status":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiProtocolsIdAssetsAssetId","summary":"Remove a monitored asset","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Protocol Monitoring"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/watchlist":{"get":{"operationId":"getApiWatchlist","summary":"List watched wallets for the caller","tags":["Watchlist"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WatchedWallet"}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiWatchlist","summary":"Watch a wallet, optionally with a push-delivery webhook","tags":["Watchlist"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"label":{"type":"string","maxLength":100},"notifyWebhookUrl":{"type":"string","format":"uri","description":"Requires the instant_alerts feature (DEVELOPER tier or above)"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WatchedWallet"}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/watchlist/{address}":{"get":{"operationId":"getApiWatchlistAddress","summary":"Check whether a specific address is watched","tags":["Watchlist"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"watched":{"type":"boolean"},"label":{"type":"string","nullable":true}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiWatchlistAddress","summary":"Unwatch a wallet","tags":["Watchlist"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"removed":{"type":"boolean"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/webhooks/helius":{"post":{"operationId":"postApiWebhooksHelius","summary":"Inbound receiver for Helius enhanced-tx push events (not for external API consumers to call — documented for completeness)","tags":["Watchlist"],"security":[{"webhookSecret":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Does NOT use the standard {data:...} envelope every other route in this API uses — Helius is the only caller, and this is a fire-and-forget ack, not a response a client branches on.","properties":{"ok":{"type":"boolean","enum":[true]},"eventsReceived":{"type":"integer"},"notificationsQueued":{"type":"integer","description":"Watcher notifications actually created — lower than eventsReceived × watcher count on a replay, since notification creation is idempotent."}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/profile":{"get":{"operationId":"getApiWalletAddressProfile","summary":"Full forensic profile for a wallet","description":"Requires DEVELOPER tier or above.","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Works for ANY address, unlike GET /api/wallet/{address}/intelligence — built from raw transaction history + the known-destination registry only, no dependency on VikingIntel scoring state, so a brand-new or non-trading address degrades to a mostly-empty profile instead of 404ing.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"entityLabel":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"}}},"firstSeen":{"type":"object","nullable":true,"properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"}}},"lastSeen":{"type":"object","nullable":true,"properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"}}},"transactionCount":{"type":"integer"},"historyTruncated":{"type":"boolean","description":"True if the backward history walk hit its page cap (10 pages, ~1,000 txs) without exhausting the wallet's full history — firstSeen is a lower bound, not a guarantee, in that case."},"topCounterparties":{"type":"array","description":"Up to 10, sorted by total tx count.","items":{"type":"object","properties":{"address":{"type":"string"},"txCount":{"type":"integer"},"totalSolIn":{"type":"number"},"totalSolOut":{"type":"number"},"knownDestination":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"}}}}}},"balanceHistory":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"},"netChangeSol":{"type":"number"}}},"description":"Oldest-first net SOL balance deltas, one per SOL-moving tx — a lightweight proxy, not real historical balance snapshots."},"activityTimeline":{"type":"array","items":{"type":"object","properties":{"signature":{"type":"string"},"timestamp":{"type":"integer"},"type":{"type":"string"},"source":{"type":"string"},"counterparties":{"type":"array","items":{"type":"string"}}}}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}":{"get":{"operationId":"getApiWalletAddress","summary":"Basic wallet lookup","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Checks the graduated Wallet table first, falling back to CandidateWallet (scored but not yet graduated) — a 404 means the address has never been through the scoring pipeline at all, not just that it scored low. reasonCodes is omitted entirely (not null) on the FREE tier.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"score":{"type":"number"},"grade":{"type":"string"},"status":{"type":"string","enum":["graduated","probation"],"description":"Or a lowercased CandidateWallet status when source is candidate."},"isActive":{"type":"boolean","description":"Always false for a candidate-sourced result."},"winRate":{"type":"number","nullable":true},"avgRoi":{"type":"number","nullable":true},"tradeCount":{"type":"integer","nullable":true},"rugExposure":{"type":"integer","nullable":true},"walletAgeDays":{"type":"integer","nullable":true},"tags":{"type":"array","items":{"type":"string"},"description":"Always empty for a candidate-sourced result — tags are only assigned after graduation."},"reasonCodes":{"type":"array","items":{"type":"object"},"description":"Omitted (not present in the response at all) on the FREE tier."},"lastActivity":{"type":"string","format":"date-time","nullable":true},"source":{"type":"string","enum":["wallet","candidate"]}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/behavior":{"get":{"operationId":"getApiWalletAddressBehavior","summary":"Behavior-tag profile","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Deterministic rule-based tags over transaction timing and program-category usage — not a machine-learned classifier (no labeled ground truth exists to train/validate one against). Tags are additive, not a forced single category.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"sampledTxCount":{"type":"integer"},"historyTruncated":{"type":"boolean"},"spanDays":{"type":"number","nullable":true,"description":"null if fewer than 2 transactions."},"avgTxPerDay":{"type":"number","nullable":true},"activityHourHistogram":{"type":"array","items":{"type":"integer"},"description":"Transaction counts by UTC hour-of-day, index 0-23. Always present (possibly all-zero) once there is at least one transaction."},"tags":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["dormant","high_frequency","regular_intervals","dex_active","nft_active","low_activity","reactivated","concentrated_hours"]},"label":{"type":"string"},"evidence":{"type":"string"}}}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/cluster-graph":{"get":{"operationId":"getApiWalletAddressCluster-graph","summary":"Sybil cluster graph (latest job + merged cross-job view)","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Same requester-scoping as GET /api/wallet/{address}/sybil — only resolves clusters from this requester's own sybil-check jobs. `cluster` is the single latest-job view; `crossJob` is additive (null unless this wallet appeared in more than one of the requester's own jobs).","properties":{"data":{"type":"object","properties":{"inCluster":{"type":"boolean"},"cluster":{"type":"object","nullable":true,"description":"ClusterView for the wallet's latest job — see GET /api/sybil/jobs/{id}'s clusters field."},"crossJob":{"type":"object","nullable":true,"description":"Present only when this wallet has been clustered by more than one of the requester's own jobs.","properties":{"clusterId":{"type":"string"},"sharedFunder":{"type":"string","nullable":true},"reason":{"type":"string"},"signals":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"memberCount":{"type":"integer"},"members":{"type":"array","items":{"type":"string"}},"smartMoneyMembers":{"type":"array","items":{"type":"string"}},"evidence":{"type":"object"},"sourceClusterIds":{"type":"array","items":{"type":"string"}},"jobCount":{"type":"integer"}}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/export":{"get":{"operationId":"getApiWalletAddressExport","summary":"Single-wallet export (json/csv/pdf via ?format=)","tags":["Reports"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Sections are opt-in via ?sections=profile,risk,riskFlags,transactions,fundFlow (default: profile,risk,transactions) — only requested sections are present in the response. Costs a real fund-flow trace whenever risk/riskFlags/fundFlow is requested (risk is in the default set), same cost/gating as /fundflow and /sybil/check. format=csv requires transactions in scope (400 otherwise) since the other sections are nested objects, not tabular rows.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"sections":{"type":"array","items":{"type":"string","enum":["profile","risk","riskFlags","transactions","fundFlow"]}},"profile":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"entityLabel":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"}}},"firstSeen":{"type":"object","nullable":true,"properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"}}},"lastSeen":{"type":"object","nullable":true,"properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"}}},"transactionCount":{"type":"integer"},"historyTruncated":{"type":"boolean"},"topCounterparties":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"txCount":{"type":"integer"},"totalSolIn":{"type":"number"},"totalSolOut":{"type":"number"},"knownDestination":{"type":"object","nullable":true}}}},"balanceHistory":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"integer"},"signature":{"type":"string"},"netChangeSol":{"type":"number"}}},"description":"Oldest-first net SOL balance deltas — a lightweight proxy, not real historical balance snapshots."},"activityTimeline":{"type":"array","items":{"type":"object","properties":{"signature":{"type":"string"},"timestamp":{"type":"integer"},"type":{"type":"string"},"source":{"type":"string"},"counterparties":{"type":"array","items":{"type":"string"}}}}}}},"risk":{"type":"object","nullable":true,"description":"Same shape as GET /api/wallet/{address}/risk."},"riskFlags":{"type":"object","nullable":true,"description":"Same shape as GET /api/wallet/{address}/risk-flags."},"transactions":{"type":"array","nullable":true,"items":{"type":"object","properties":{"signature":{"type":"string"},"timestamp":{"type":"integer"},"kind":{"type":"string","enum":["sol","spl"]},"direction":{"type":"string"},"counterparty":{"type":"string"},"amount":{"type":"number","description":"SOL for kind=sol, raw token amount for kind=spl."},"mint":{"type":"string","nullable":true},"txType":{"type":"string","nullable":true}}}},"fundFlow":{"type":"object","nullable":true,"description":"Same shape as GET /api/wallet/{address}/fundflow."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/fingerprint":{"get":{"operationId":"getApiWalletAddressFingerprint","summary":"Trading fingerprint (hold time, sizing, DEX routing, style tags) + realized/unrealized PnL","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Was already built in packages/forensics but never wired to a route until this pass of the audit — see this route's own file comment. Costs two separate tx-history fetches per request (fingerprint fetches its own; PnL needs txs+holdings passed in) — a known, accepted inefficiency, not a bug.","properties":{"data":{"type":"object","properties":{"fingerprint":{"type":"object","properties":{"address":{"type":"string"},"sampledTxCount":{"type":"integer"},"historyTruncated":{"type":"boolean"},"holdTime":{"type":"object","properties":{"avgHoldSeconds":{"type":"number","nullable":true},"medianHoldSeconds":{"type":"number","nullable":true},"completedPositions":{"type":"integer"},"stillHeldPositions":{"type":"integer"}},"description":"Only completed buy→sell positions contribute to avg/median."},"holdTimePositions":{"type":"array","items":{"type":"object","properties":{"mint":{"type":"string"},"buyTimestamp":{"type":"integer"},"sellTimestamp":{"type":"integer","nullable":true},"holdSeconds":{"type":"integer","nullable":true}}}},"tradesPerDay":{"type":"number","nullable":true},"tokenDiversity":{"type":"integer","description":"Distinct mints traded in the sampled window."},"positionSize":{"type":"object","properties":{"sampledTradeCount":{"type":"integer"},"avgSol":{"type":"number","nullable":true},"medianSol":{"type":"number","nullable":true},"minSol":{"type":"number","nullable":true},"maxSol":{"type":"number","nullable":true}},"description":"SOL-denominated, native transfers only — SPL amounts aren't convertible to a common unit without a price, so they're excluded rather than mixed in."},"dexPreference":{"type":"array","items":{"type":"object","properties":{"programId":{"type":"string"},"label":{"type":"string"},"swapCount":{"type":"integer"}}},"description":"Sorted by swapCount descending."},"styleTags":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["scalper","momentum","early_buyer","swing","sniper","long_term_holder"]},"label":{"type":"string"},"evidence":{"type":"string"}}}}}},"pnl":{"type":"object","properties":{"realized":{"type":"object","properties":{"pricedTradeCount":{"type":"integer"},"unpricedTradeCount":{"type":"integer"},"totalRealizedPnlUsd":{"type":"number","nullable":true},"winCount":{"type":"integer"},"lossCount":{"type":"integer"},"winRate":{"type":"number","nullable":true,"description":"VikingIntel-derived from this wallet's own matched, priced trades — a separate number from the Wallet.winRate scalar elsewhere in the API, not a replacement for it; a large divergence is itself worth noticing."},"profitFactor":{"type":"number","nullable":true,"description":"sum(winning PnL) / abs(sum(losing PnL)). Null if no losing trades or no priced trades."},"bestTrade":{"type":"object","nullable":true},"worstTrade":{"type":"object","nullable":true},"maxDrawdownUsd":{"type":"number","nullable":true,"description":"Largest peak-to-trough decline in the cumulative realized-PnL curve — not a mark-to-market drawdown on open positions."},"maxLossStreak":{"type":"integer"}}},"realizedTrades":{"type":"array","items":{"type":"object","properties":{"mint":{"type":"string"},"buyTimestamp":{"type":"integer"},"buySignature":{"type":"string"},"sellTimestamp":{"type":"integer"},"sellSignature":{"type":"string"},"tokenAmount":{"type":"number"},"buyPriceUsd":{"type":"number","nullable":true},"sellPriceUsd":{"type":"number","nullable":true},"realizedPnlUsd":{"type":"number","nullable":true}}}},"unrealized":{"type":"object","properties":{"positions":{"type":"array","items":{"type":"object","properties":{"mint":{"type":"string"},"heldAmount":{"type":"number"},"avgEntryPriceUsd":{"type":"number","nullable":true},"currentPriceUsd":{"type":"number","nullable":true},"unrealizedPnlUsd":{"type":"number","nullable":true}}}},"totalUnrealizedPnlUsd":{"type":"number","nullable":true},"pricedPositionCount":{"type":"integer"},"unpricedPositionCount":{"type":"integer"}}},"rugExposure":{"type":"object","nullable":true,"description":"null when no independent LP-pull cross-check was available for this request — never faked from realized losses alone, since an ordinary bad trade isn't a rug without independent pull evidence.","properties":{"rugLossTrades":{"type":"array","items":{"type":"object"}},"totalRugLossUsd":{"type":"number","nullable":true},"rugLossCount":{"type":"integer","description":"Separate, VikingIntel-derived count from the Wallet.rugExposure scalar elsewhere in the API — not a replacement for it."}}}}},"portfolio":{"type":"object","description":"Current portfolio value + per-token allocation (7.2), derived from pnl.unrealized.positions' already-fetched current prices — no extra price lookups.","properties":{"holdings":{"type":"array","items":{"type":"object","properties":{"mint":{"type":"string"},"heldAmount":{"type":"number"},"valueUsd":{"type":"number","nullable":true},"allocationPct":{"type":"number","nullable":true}}}},"totalValueUsd":{"type":"number","nullable":true,"description":"Sum of priced holdings only — a lower bound when any holding is unpriced, never a fabricated total."},"pricedHoldingCount":{"type":"integer"},"unpricedHoldingCount":{"type":"integer"}}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/fundflow":{"get":{"operationId":"getApiWalletAddressFundflow","summary":"Fund-flow trace for a wallet","description":"Requires DEVELOPER tier or above.","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"GET convenience wrapper around the same traceFundFlow() used by POST /api/fundflow/trace — walks outbound SOL transfers hop-by-hop (breadth-first, largest transfers first, up to 4 hops) from this address, checking each hop against the known-destinations registry. Not exhaustive — designed to surface \"did this reach a CEX/bridge\" fast, not reconstruct every lamport's path.","properties":{"data":{"type":"object","properties":{"root":{"type":"string"},"hops":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"amountSol":{"type":"number"},"signature":{"type":"string"},"timestamp":{"type":"string"},"toIsKnownDestination":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","description":"e.g. cex_hot_wallet | bridge | mixer | sanctioned | program | other."}}}}}},"hitKnownDestinations":{"type":"array","items":{"type":"object"},"description":"Subset of hops above whose destination matched the known-destinations registry — same FlowHop shape."},"truncated":{"type":"boolean","description":"True if the depth or per-hop branch cap cut the trace short."},"completeness":{"type":"string","enum":["complete","partial","truncated"],"description":"Whether every branch resolved to a dead end or a known CEX/bridge/mixer exit ('complete'), some but not all did ('partial'), or none did ('truncated')."},"leaves":{"type":"array","description":"One entry per branch endpoint: { address, reason, knownDestination }."},"warning":{"type":"string","description":"Present only when hitKnownDestinations is empty, noting the registry may simply not be populated for this trace rather than the wallet being clean."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/history":{"get":{"operationId":"getApiWalletAddressHistory","summary":"Transaction history","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"This wallet's early-buy trading history (not raw on-chain transactions — see GET /api/wallet/{address}/transactions for that), newest first, paginated.","properties":{"data":{"type":"array","items":{"type":"object","properties":{"tokenMint":{"type":"string"},"buyerRank":{"type":"integer"},"solAmount":{"type":"number"},"usdValueAtBuy":{"type":"number","nullable":true},"usdValueAtExit":{"type":"number","nullable":true},"roi":{"type":"number","nullable":true},"isRug":{"type":"boolean"},"timestamp":{"type":"integer"}}}},"meta":{"type":"object","properties":{"address":{"type":"string"},"total":{"type":"integer"},"limit":{"type":"integer","description":"1-200, default 100."},"offset":{"type":"integer"},"count":{"type":"integer"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/intelligence":{"get":{"operationId":"getApiWalletAddressIntelligence","summary":"Combined intelligence summary","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"One-call aggregate of scoring, reputation, risk, fund flow, token activity, and historical outcomes. Never 404s — an address with no VikingIntel scoring history still returns a fully-shaped result with scored: false rather than erroring, since scoring is currently paused and every fresh address would otherwise 404.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"score":{"type":"number","nullable":true},"grade":{"type":"string","nullable":true},"scored":{"type":"boolean"},"status":{"type":"string","enum":["graduated","probation","unscored"],"description":"Or a lowercased CandidateWallet status string when the address is a candidate."},"confidence":{"type":"string","enum":["unknown","low","medium","high"]},"reputation":{"type":"object","properties":{"isFlaggedFarmer":{"type":"boolean"},"isFlaggedScammer":{"type":"boolean"},"sybilClusterConfidence":{"type":"number","nullable":true}}},"risk":{"type":"object","properties":{"rugExposureCount":{"type":"integer"},"tags":{"type":"array","items":{"type":"string"}}}},"gmgnVerification":{"type":"object","properties":{"available":{"type":"boolean","enum":[false]},"note":{"type":"string"}},"description":"Cross-reference data unavailable."},"reasonCodes":{"type":"array","items":{"type":"object"}},"stats":{"type":"object","properties":{"winRate":{"type":"number","nullable":true},"avgRoi":{"type":"number","nullable":true},"tradeCount":{"type":"integer","nullable":true},"walletAgeDays":{"type":"integer","nullable":true},"alertCount":{"type":"integer","nullable":true},"alertSuccessRate":{"type":"number","nullable":true}}},"relationships":{"type":"object","properties":{"sharedFunderWallets":{"type":"array","items":{"type":"string"},"description":"Other wallets funded by this wallet's same first-funder, up to 20."}}},"fundFlow":{"type":"object","properties":{"hops":{"type":"array","items":{"type":"object"},"description":"Same FlowHop shape as GET /api/wallet/{address}/fundflow."},"hitKnownDestinations":{"type":"array","items":{"type":"object"}},"truncated":{"type":"boolean"},"note":{"type":"string"}}},"tokenActivity":{"type":"array","items":{"type":"object","properties":{"tokenMint":{"type":"string"},"buyerRank":{"type":"integer"},"solAmount":{"type":"number"},"roi":{"type":"number","nullable":true},"isRug":{"type":"boolean"},"timestamp":{"type":"integer"}}}},"historicalOutcomes":{"type":"array","description":"Most recent 20 — same shape as GET /api/wallet/{address}/outcomes.","items":{"type":"object"}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/outcomes":{"get":{"operationId":"getApiWalletAddressOutcomes","summary":"Historical alert outcomes for a wallet","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Only graduated (scored) wallets can have alerts, by design — a candidate/unscored address returns an empty, explained result rather than a 404.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"count":{"type":"integer"},"outcomes":{"type":"array","description":"Most recent 20.","items":{"type":"object","properties":{"txSignature":{"type":"string"},"alertType":{"type":"string"},"tokenSymbol":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"outcome":{"type":"object","nullable":true,"description":"null until the outcome-observation job has measured price movement after this alert.","properties":{"priceAtAlert":{"type":"number","nullable":true},"pct1h":{"type":"number","nullable":true},"pct6h":{"type":"number","nullable":true},"pct24h":{"type":"number","nullable":true}}}}}},"note":{"type":"string","description":"Present only when the wallet has no graduated record."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/portfolio":{"get":{"operationId":"getApiWalletAddressPortfolio","summary":"Current token holdings","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Current holdings snapshot only — deliberately no PnL/entry-price fields, since no price feed exists in VikingIntel to compute those honestly.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"solBalance":{"type":"number","nullable":true,"description":"null only if the balance RPC call itself failed."},"tokenHoldings":{"type":"array","description":"Non-zero balances only.","items":{"type":"object","properties":{"mint":{"type":"string"},"amount":{"type":"string","description":"Raw base units, as a string for precision safety."},"decimals":{"type":"integer"},"uiAmount":{"type":"number","nullable":true}}}},"fetchFailed":{"type":"boolean","description":"True if the token-account RPC call itself failed (distinct from genuinely zero holdings)."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/programs":{"get":{"operationId":"getApiWalletAddressPrograms","summary":"On-chain program interaction history","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Aggregates which programs a wallet's transactions touch and labels the ones in a small verified registry (Solana native programs, Jupiter v6, Raydium AMM/CLMM/CPMM, Orca Whirlpools, Meteora DLMM, pump.fun) — not exhaustive; unregistered programs surface with their raw programId rather than a guessed label.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"programs":{"type":"array","description":"Sorted by txCount descending.","items":{"type":"object","properties":{"programId":{"type":"string"},"label":{"type":"string"},"category":{"type":"string","enum":["system","token","dex","unknown"]},"txCount":{"type":"integer"},"firstSeen":{"type":"integer","description":"Unix seconds."},"lastSeen":{"type":"integer"}}}},"categoryBreakdown":{"type":"object","description":"Tx-touch counts per category, not unique-program counts.","properties":{"system":{"type":"integer"},"token":{"type":"integer"},"dex":{"type":"integer"},"unknown":{"type":"integer"}}},"historyTruncated":{"type":"boolean"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/reputation":{"get":{"operationId":"getApiWalletAddressReputation","summary":"Reputation summary","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Reputation summary for one wallet. (The legacy GET /api/wallet-reputation/{address} path is removed and returns 404.)","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"knownWallet":{"type":"boolean","deprecated":true,"description":"DEPRECATED — renamed inScoringData. About scoring data only: check knownEntity for registry labels."},"inScoringData":{"type":"boolean","description":"True when VikingIntel's scoring pipeline has a row for this address (about scoring data only, not registry labels — see knownEntity)."},"knownEntity":{"type":"object","nullable":true,"description":"Present when the address is in the verified destination registry.","properties":{"label":{"type":"string"},"type":{"type":"string"}}},"score":{"type":"number","description":"Only present when inScoringData is true."},"walletAgeDays":{"type":"integer","nullable":true},"isFlaggedFarmer":{"type":"boolean"},"sybilClusterConfidence":{"type":"number","nullable":true},"isFlaggedScammer":{"type":"boolean"},"rugExposureCount":{"type":"integer"},"recommendation":{"type":"string","enum":["allow","review","block","no_data"]},"note":{"type":"string"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/risk":{"get":{"operationId":"getApiWalletAddressRisk","summary":"Composite risk score","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Blends trading reputation, sybil cluster confidence, cashout exposure, and known-destination exposure into one 0-100 score. Components with nothing to go on are marked present: false and excluded, with remaining weights renormalized, rather than treated as safe or risky by default.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"score":{"type":"number","description":"0-100, higher = riskier."},"level":{"type":"string","enum":["unknown","low","medium","high","critical"],"description":"'unknown' when no component had evidence (scored:false) — absence of evidence, not low risk."},"components":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"number"},"weight":{"type":"number"},"present":{"type":"boolean"},"detail":{"type":"string"}}}},"cashout":{"type":"object","description":"Fund-flow hops landing on known cashout-relevant destinations, by category — each hit has the same FlowHop shape documented on GET /api/wallet/{address}/fundflow.","properties":{"root":{"type":"string"},"cexHits":{"type":"array","items":{"type":"object"},"description":"Hops landing on a CEX hot wallet."},"bridgeExits":{"type":"array","items":{"type":"object"},"description":"Hops landing on a bridge program; destination-chain context is not available."},"mixerHits":{"type":"array","items":{"type":"object"},"description":"Hops landing on a labeled mixer."},"sanctionedHits":{"type":"array","items":{"type":"object"},"description":"Hops landing on an OFAC/sanctions-listed address."}}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/risk-flags":{"get":{"operationId":"getApiWalletAddressRisk-flags","summary":"Individual risk flags behind the composite score","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Answers \"which specific conditions fired, and what evidence backs each\" — for justifying a review decision — as opposed to /risk's single blended ranking number. Reads the same underlying signals as /risk; adds no new detection.","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"flags":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"label":{"type":"string"},"severity":{"type":"string","enum":["info","low","medium","high","critical"]},"evidence":{"type":"array","items":{"type":"string"}}}}},"highestSeverity":{"type":"string","enum":["info","low","medium","high","critical"],"nullable":true}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/score-breakdown":{"get":{"operationId":"getApiWalletAddressScore-breakdown","summary":"Score component breakdown","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"address":{"type":"string"},"score":{"type":"number"},"inputs":{"type":"object","properties":{"winRate":{"type":"number","nullable":true},"avgRoi":{"type":"number","nullable":true},"tradeCount":{"type":"integer","nullable":true},"rugExposure":{"type":"integer","nullable":true},"earlyEntryScore":{"type":"number","nullable":true}}},"breakdown":{"type":"object","nullable":true,"description":"null when this wallet was last scored by the simpler daily chain-only re-scoring path rather than the full pipeline; score/inputs above are still meaningful either way.","properties":{"winRateScore":{"type":"number"},"roiScore":{"type":"number"},"earlyEntryScore":{"type":"number"},"consistencyScore":{"type":"number"},"activityScore":{"type":"number"},"safetyScore":{"type":"number"},"ageMultiplier":{"type":"number"}}},"computedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/score-history":{"get":{"operationId":"getApiWalletAddressScore-history","summary":"Score over time","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"Up to the 30 most recent scoring computations, oldest-first (for left-to-right charting).","items":{"type":"object","properties":{"score":{"type":"number"},"computedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/metric-history":{"get":{"operationId":"getApiWalletAddressMetric-history","summary":"Balance/behavior/counterparty trend over time","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"Up to the 30 most recent snapshots for the requested kind (?kind=BALANCE|BEHAVIOR|COUNTERPARTY), oldest-first.","items":{"type":"object","properties":{"payload":{"type":"object","description":"Shape depends on kind — see WalletMetricHistory.payload."},"computedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/source-of-funds":{"get":{"operationId":"getApiWalletAddressSource-of-funds","summary":"Backward funding trace to likely source","description":"Requires DEVELOPER tier or above.","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Backward-tracing complement to GET /api/wallet/{address}/fundflow (which traces forward, toward destinations). Each hop can cost a live Helius page fetch, same cost/gating as that route.","properties":{"data":{"type":"object","properties":{"root":{"type":"string"},"hops":{"type":"array","description":"root -> funder -> funder's funder -> ..., in that order.","items":{"type":"object","properties":{"address":{"type":"string","description":"The wallet this hop's funding info is FOR."},"funderAddress":{"type":"string"},"amount":{"type":"number","nullable":true},"signature":{"type":"string","nullable":true},"timestamp":{"type":"string","nullable":true},"funderIsKnownDestination":{"type":"object","nullable":true,"properties":{"address":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"}}}}}},"boundary":{"type":"object","nullable":true,"description":"The hop whose funder is a known destination — where an on-chain-only trace can go no further and the registry gives the best available real-world attribution. Null if the chain never reached one within MAX_DEPTH."},"deadEnd":{"type":"boolean","description":"True if the chain ended because a wallet had no resolvable funder — either its genuine origin, or a page-cap limit was hit trying to find one."},"truncated":{"type":"boolean","description":"True if MAX_DEPTH (6) was reached before finding a boundary or dead end — the true source may be further back than this trace shows."},"warning":{"type":"string","description":"Present only when there's no boundary but hops is non-empty — the CEX/bridge registry may not cover the true source yet."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/entity-graph":{"get":{"operationId":"getApiCasesIdEntity-graph","summary":"Entity graph for the case subjects","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/sybil":{"get":{"operationId":"getApiWalletAddressSybil","summary":"Latest sybil-cluster membership for a wallet","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Scoped to the caller's own past /api/sybil/check jobs, not a global lookup — a wallet only clustered by a different requester's job shows inCluster: false here.","properties":{"address":{"type":"string"},"inCluster":{"type":"boolean"},"confidence":{"type":"number","nullable":true},"reason":{"type":"string","nullable":true},"coMembers":{"type":"array","items":{"type":"string"},"description":"Up to 50 co-member addresses."}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/token-risk":{"get":{"operationId":"getApiWalletAddressToken-risk","summary":"Rug-check flags for tokens this wallet holds","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Answers \"how much rug exposure does this wallet have from tokens it has already bought\" — derived from this wallet's own early-buy history. NOT the same question as GET /api/token-risk/{mint}, which scans one token's own rug vectors (LP lock, mint authority) independent of who holds it.","properties":{"address":{"type":"string"},"rugExposureCount":{"type":"integer"},"totalTokenInteractions":{"type":"integer"},"rugTokens":{"type":"array","items":{"type":"object","properties":{"tokenMint":{"type":"string"},"timestamp":{"type":"integer"}}}},"note":{"type":"string"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/transactions":{"get":{"operationId":"getApiWalletAddressTransactions","summary":"Raw transaction list","tags":["Wallet"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Raw on-chain SOL + SPL transaction history, unified/normalized and paginated — distinct from GET /api/wallet/{address}/history, which is this wallet's early-buy trading history, not raw transactions.","properties":{"data":{"type":"array","items":{"type":"object","properties":{"signature":{"type":"string"},"timestamp":{"type":"integer"},"kind":{"type":"string","enum":["sol","spl"]},"direction":{"type":"string"},"counterparty":{"type":"string"},"amount":{"type":"number"},"mint":{"type":"string","nullable":true},"txType":{"type":"string","nullable":true}}}},"meta":{"type":"object","properties":{"address":{"type":"string"},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true},"count":{"type":"integer"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet-reputation/{address}":{"get":{"operationId":"getApiWallet-reputationAddress","summary":"REMOVED — returns 404 pointing to /api/wallet/{address}/intelligence","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/explorer/wallet/{address}":{"get":{"operationId":"getApiExplorerWalletAddress","summary":"DEPRECATED — identical to GET /api/wallet/{address}/intelligence; kept for existing consumers only","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Explorer"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token-risk/{mint}":{"get":{"operationId":"getApiToken-riskMint","summary":"Composite token risk assessment","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token/{mint}/lineage":{"get":{"operationId":"getApiTokenMintLineage","summary":"Mint lineage (creation and distribution path)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token/{mint}/lineage-preview":{"get":{"operationId":"getApiTokenMintLineage-preview","summary":"Anonymous preview for token mint lineage (depth-limited)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token/{mint}/concentration":{"get":{"operationId":"getApiTokenMintConcentration","summary":"Holder concentration analysis","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token/{mint}/lp-pull-candidates":{"get":{"operationId":"getApiTokenMintLp-pull-candidates","summary":"LP-pull (rug) candidate signals","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/token/{mint}/intelligence":{"get":{"operationId":"getApiTokenMintIntelligence","summary":"Deployer dossier, launch fingerprint, and historical comparison","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Token Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"mint","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/graph/{address}":{"get":{"operationId":"getApiGraphAddress","summary":"Graph adjacency for an address","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Explorer"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/search":{"get":{"operationId":"getApiSearch","summary":"Global search across wallets, labels, clusters, tokens","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Explorer"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/known-destinations":{"get":{"operationId":"getApiKnown-destinations","summary":"Verified known-destination registry entries","description":"Requires INTERNAL tier or above. Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Known Destinations"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiKnown-destinations","summary":"Add/update a verified destination (registry write)","description":"Requires INTERNAL tier or above. Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Known Destinations"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiKnown-destinations","summary":"Revoke a verified destination (auditable revocation trail)","description":"Requires INTERNAL tier or above. Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Known Destinations"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/outcomes/import":{"post":{"operationId":"postApiOutcomesImport","summary":"Bulk-import alert outcome data","description":"Requires INTERNAL tier or above.","tags":["System"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jobId","outcomes"],"description":"Ownership-scoped through jobId (must belong to the caller). Append-only — every outcome becomes a new row with source \"customer\"; nothing is ever updated in place.","properties":{"jobId":{"type":"string"},"outcomes":{"type":"array","minItems":1,"maxItems":1000,"description":"Max 1000 per request — split larger imports into multiple calls.","items":{"type":"object","required":["address","outcome"],"properties":{"address":{"type":"string"},"outcome":{"type":"string","enum":["RECEIVED_DISTRIBUTION","WITHHELD","SWEPT_TO_SINK","DORMANT","APPEAL_UPHELD","APPEAL_REJECTED"]},"detail":{"type":"object","description":"Arbitrary caller-supplied context, stored as-is."}}}}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"jobId":{"type":"string"},"imported":{"type":"integer"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/api-keys":{"get":{"operationId":"getApiApi-keys","summary":"Caller's API keys","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["API Keys"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiApi-keys","summary":"Create an API key — session-cookie-only (see NOTE on the {id} routes above), not Bearer-key-authenticatable","tags":["Account"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":100,"description":"Defaults to \"Untitled key\" if omitted."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","description":"Max 10 active keys per account, enforced atomically (a prior read-then-check-then-write race that could let two concurrent POSTs both land under the cap was fixed — count check and insert are now one atomic repository call).","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"tier":{"type":"string","enum":["FREE"],"description":"New keys always start on FREE."},"rawKey":{"type":"string","description":"Shown exactly once — never re-fetchable after this response."},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/api-keys/{id}":{"patch":{"operationId":"patchApiApi-keysId","summary":"Rename an API key","tags":["Account"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":100}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiApi-keysId","summary":"Revoke an API key","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["API Keys"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts":{"get":{"operationId":"getApiAlerts","summary":"Incident alerts for the caller (newest first)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts/{id}":{"get":{"operationId":"getApiAlertsId","summary":"Incident alert detail","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts/{id}/acknowledge":{"post":{"operationId":"postApiAlertsIdAcknowledge","summary":"Acknowledge an incident alert","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts/{id}/confirm":{"post":{"operationId":"postApiAlertsIdConfirm","summary":"Confirm an incident alert as a real incident","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts/{id}/dismiss":{"post":{"operationId":"postApiAlertsIdDismiss","summary":"Dismiss an incident alert as noise","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/alerts/{id}/investigate":{"post":{"operationId":"postApiAlertsIdInvestigate","summary":"Open an investigation case from an incident alert","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Alerts"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/billing/entitlements":{"get":{"operationId":"getApiBillingEntitlements","summary":"Current plan and feature entitlements for the authenticated identity","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Billing"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/billing/subscription":{"get":{"operationId":"getApiBillingSubscription","summary":"Current subscription record","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Billing"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/billing/checkout":{"post":{"operationId":"postApiBillingCheckout","summary":"Start an OxaPay checkout for a plan upgrade","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Billing"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/billing/cancel":{"post":{"operationId":"postApiBillingCancel","summary":"Cancel the active subscription","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Billing"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/billing/renewal-invoices":{"get":{"operationId":"getApiBillingRenewal-invoices","summary":"List upcoming renewal invoices for the current user","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Billing"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/bundle":{"get":{"operationId":"getApiCasesIdBundle","summary":"Download the tamper-evident evidence bundle (zip)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/evidence/presign":{"post":{"operationId":"postApiCasesIdEvidencePresign","summary":"Presign an evidence upload URL","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/evidence/confirm":{"post":{"operationId":"postApiCasesIdEvidenceConfirm","summary":"Confirm an evidence upload (chain-of-custody hash recorded)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/findings/{findingId}":{"patch":{"operationId":"patchApiCasesIdFindingsFindingId","summary":"Update a finding status (human review)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"findingId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/osint":{"get":{"operationId":"getApiCasesIdOsint","summary":"OSINT evidence collected for the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiCasesIdOsint","summary":"Enqueue an OSINT enrichment job for the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/osint/{jobId}":{"get":{"operationId":"getApiCasesIdOsintJobId","summary":"OSINT job status (requester-scoped)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"jobId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/response":{"get":{"operationId":"getApiCasesIdResponse","summary":"Response packages for the case","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiCasesIdResponse","summary":"Create a response package","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/response/{packageId}/drafts":{"get":{"operationId":"getApiCasesIdResponsePackageIdDrafts","summary":"Drafts in a response package","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiCasesIdResponsePackageIdDrafts","summary":"Create a draft in a response package","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/response/{packageId}/drafts/{draftId}/approve":{"post":{"operationId":"postApiCasesIdResponsePackageIdDraftsDraftIdApprove","summary":"Approve a draft (human gate before send)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"}},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/response/{packageId}/drafts/{draftId}/send":{"post":{"operationId":"postApiCasesIdResponsePackageIdDraftsDraftIdSend","summary":"Send an APPROVED draft — never auto-sent","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"}},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/watch":{"post":{"operationId":"postApiCasesIdWatch","summary":"Watch/unwatch the case for updates","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Cases"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/copilot":{"post":{"operationId":"postApiCasesIdCopilot","summary":"Ask the investigation copilot a question over case data","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Copilot"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/copilot/drafts":{"post":{"operationId":"postApiCasesIdCopilotDrafts","summary":"Generate a cited draft document via copilot","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Copilot"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/cases/{id}/copilot/findings":{"post":{"operationId":"postApiCasesIdCopilotFindings","summary":"Persist copilot output as INFERRED findings (never OBSERVED)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Copilot"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/transaction/{signature}":{"get":{"operationId":"getApiTransactionSignature","summary":"Transaction intelligence (transfers, swaps, programs)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Transaction Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"signature","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/fundflow/trace-preview":{"post":{"operationId":"postApiFundflowTrace-preview","summary":"Anonymous depth-limited fund-flow preview (signed-in callers get 403 — use POST /api/fundflow/trace)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Fund Flow"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/intelligence/calibration":{"get":{"operationId":"getApiIntelligenceCalibration","summary":"Detector calibration runs","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/intelligence/corrections":{"get":{"operationId":"getApiIntelligenceCorrections","summary":"Correction history applied to detection outputs","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/intelligence/patterns":{"get":{"operationId":"getApiIntelligencePatterns","summary":"Detected laundering patterns","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/intelligence/relationships":{"get":{"operationId":"getApiIntelligenceRelationships","summary":"Typed entity relationships","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/known-destinations/candidates":{"get":{"operationId":"getApiKnown-destinationsCandidates","summary":"Machine-generated label candidates awaiting human review","description":"Requires INTERNAL tier or above. Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Known Destinations"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiKnown-destinationsCandidates","summary":"Promote/discard a machine candidate (the only machine→verified promotion point)","description":"Requires INTERNAL tier or above. Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Known Destinations"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/my-labels":{"get":{"operationId":"getApiMy-labels","summary":"The account's private destination labels","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Private Labels"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"post":{"operationId":"postApiMy-labels","summary":"Upsert a private label override","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Private Labels"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}},"delete":{"operationId":"deleteApiMy-labels","summary":"Delete a private label","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Private Labels"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/sybil/jobs/{id}/verify-outcomes":{"post":{"operationId":"postApiSybilJobsIdVerify-outcomes","summary":"Record per-address verification outcomes","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Sybil Detection"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/trace/public":{"post":{"operationId":"postApiTracePublic","summary":"Submit a Trace-My-Funds signature (anonymous allowed; daily quota charged at enqueue, refunded on provider failure)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Trace My Funds"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/trace/public/job/{id}":{"get":{"operationId":"getApiTracePublicJobId","summary":"Poll a public trace job (capability-token authorized)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Trace My Funds"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/trace/public/{caseId}":{"get":{"operationId":"getApiTracePublicCaseId","summary":"Public result page payload for a completed trace (capability-token authorized)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Trace My Funds"],"security":[],"parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/trace/claim":{"post":{"operationId":"postApiTraceClaim","summary":"Claim a finished trace into a signed-in account (capability token required)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Trace My Funds"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/trace/{caseId}/letter":{"get":{"operationId":"getApiTraceCaseIdLetter","summary":"Freeze-request letter derived from the trace narrative (capability-token authorized)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Trace My Funds"],"security":[],"parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/tracer":{"post":{"operationId":"postApiTracer","summary":"Interactive tracer hop expansion (anon preview posture, gated after access check)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Tracer"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/tracer/watch":{"post":{"operationId":"postApiTracerWatch","summary":"Watch a tracer address for changes","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Tracer"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/triage/{id}":{"get":{"operationId":"getApiTriageId","summary":"Poll a bulk-triage job","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Bulk Triage"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/triage":{"post":{"operationId":"postApiTriage","summary":"Submit address lists for ranked bulk triage","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Bulk Triage"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/quick-screen":{"post":{"operationId":"postApiQuick-screen","summary":"Batch screen addresses (insufficientData when no evidence)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Quick Screen"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/walk":{"post":{"operationId":"postApiWalletAddressWalk","summary":"Enqueue an async wallet-intelligence walk","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/deposit-sweep":{"get":{"operationId":"getApiWalletAddressDeposit-sweep","summary":"CEX deposit-sweep candidates observed for the wallet","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/{address}/funding":{"get":{"operationId":"getApiWalletAddressFunding","summary":"Funding source resolution for the wallet","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/walk/{jobId}":{"get":{"operationId":"getApiWalletWalkJobId","summary":"Poll a wallet walk job","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/wallet/compare-preview":{"post":{"operationId":"postApiWalletCompare-preview","summary":"Anonymous preview for wallet comparison (age, risk, shared counterparties)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Wallet Intelligence"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/verify/report":{"get":{"operationId":"getApiVerifyReport","summary":"Third-party report-hash verification (deliberately public, minimal fields)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["Verification"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/analytics/event":{"post":{"operationId":"postApiAnalyticsEvent","summary":"First-party UI funnel event ingest (telemetry opt-out: never spends investigation quota)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["System"],"security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/webhooks/0xpay":{"post":{"operationId":"postApiWebhooks0xpay","summary":"OxaPay payment webhook (HMAC-SHA512 verified, idempotent duplicates return 200)","description":"Response schema not yet documented in this spec — see /docs for a worked example.","tags":["System"],"security":[{"webhookSecret":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}},"/api/usage":{"get":{"operationId":"getApiUsage","summary":"Current quota usage across all metered features","tags":["Account"],"security":[{"apiKey":[]},{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"plan":{"type":"string"},"features":{"type":"object"},"apiCredits":{"type":["object","null"]},"resetAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Authenticated but not authorized (tier/feature gate)"},"404":{"description":"Not found, or exists but not owned by the caller"}}}}}}