{"openapi":"3.1.0","info":{"title":"ChainAnalyzer Enterprise REST API","version":"2026.08.14","summary":"Multi-chain AML screening, monitoring, and investigation across 12 chains.","description":"REST API for compliance teams and virtual-asset service providers.\n\n**Authentication.** Every operation except the health check, the webhook event catalog, and the read-only graph endpoints requires a `tfk_` API key in the `X-API-Key` header (`Authorization: Bearer tfk_…` is also accepted). Create and revoke keys from the dashboard or via `/api-keys`. Keys are shown once and stored only as hashes; changes to a key or its plan propagate within 5 minutes.\n\n**Score convention.** risk_score is 0-100 where **higher = riskier** (100 = most dangerous). Responses that carry historical or batch rows also include `score_semantics: \"risk_0to100\"` so integrators can assert the convention.\n\n**Rate limits.** Per key, per minute: Pro 60, Business 120, Enterprise 300. The screening hot path (`/public/presign/check`) draws on a separate bucket at 4x that rate, so per-transaction screening never starves batch or investigation traffic. Successful responses on the newer endpoints carry `X-RateLimit-Limit` and `X-RateLimit-Remaining`; a 429 carries `Retry-After`.\n\n**Monthly quotas.** Scans per month: Free 10, Starter 200, Pro 1,000, Business 3,000, Enterprise unlimited. A synchronous or asynchronous single scan costs 1; batch and case bulk scans cost 1 per address, charged only for addresses that complete. Screening, usage, history, report downloads, and every read-only endpoint cost nothing. Each operation declares its cost as `x-quota-cost`. Check remaining balance with `GET /public/usage`.\n\n**Long-running work.** Anything that can outlast the request deadline returns 202 with a job id and a status endpoint to poll: asynchronous scans, case bulk scans, and report package generation. A job reported as `stale` lost its worker and can be retried.","contact":{"name":"refinancier, inc.","url":"https://chain-analyzer.com/docs/api","email":"info@refinancier.jp"}},"servers":[{"url":"https://chain-analyzer.com/api/v1","description":"Production"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"scan","description":"Address scanning — synchronous, asynchronous, batch, and history"},{"name":"screening","description":"Low-latency verdicts on the transaction hot path"},{"name":"usage","description":"Quota and plan-limit introspection"},{"name":"monitoring","description":"Watchlist, Follow Mode, and webhooks"},{"name":"transfers","description":"Token-transfer queries over an explicit date range — by address, or contract-wide"},{"name":"investigation","description":"Cases, exposure, and reporting"},{"name":"graph","description":"Fund-flow paths and address relationships"},{"name":"keys","description":"API key lifecycle"}],"paths":{"/public/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"Liveness probe. No authentication required.","tags":["scan"],"security":[],"responses":{"200":{"description":"Service is up.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"service":{"type":"string"},"redis":{"type":"boolean"}}}}}}}}},"/public/scan":{"get":{"operationId":"scanAddressGet","summary":"Scan an address (query form)","description":"Synchronous scan, identical to the POST form. Results are cached for 30 minutes; a cache hit returns `cached: true` and still counts against the rate limit. Heavy addresses can exceed the ~240s ingress deadline — use `POST /public/scan/async` for those.","tags":["scan"],"parameters":[{"name":"address","in":"query","required":true,"description":"Blockchain address to scan.","schema":{"type":"string"}},{"name":"chain","in":"query","required":false,"description":"Chain hint. Auto-detected when omitted.","schema":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]}},{"name":"lang","in":"query","required":false,"description":"Language of detection descriptions.","schema":{"type":"string","enum":["en","ja"],"default":"en"}}],"x-quota-cost":1,"responses":{"200":{"description":"Scan result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"400":{"description":"Empty address, unrecognised format, or chain/format mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Address not found on the target chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Scan pipeline failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"scanAddress","summary":"Scan an address","description":"Synchronous scan. Charges 1 scan against the monthly quota. risk_score is 0-100 where **higher = riskier** (100 = most dangerous). Responses that carry historical or batch rows also include `score_semantics: \"risk_0to100\"` so integrators can assert the convention.","tags":["scan"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"}}}},"x-quota-cost":1,"responses":{"200":{"description":"Scan result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"400":{"description":"Empty address, unrecognised format, or chain/format mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Address not found on the target chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Scan pipeline failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scan/async":{"post":{"operationId":"scanAddressAsync","summary":"Scan an address asynchronously","description":"Queues a single-address scan and returns 202 immediately. Use this for addresses that would exceed the synchronous ingress deadline (large UTXO sets, very high-frequency EVM accounts). The job is persisted, so it survives a replica restart. Charges the same 1 scan as the synchronous call, billed once on success. Available on any API-enabled plan, including plans without batch scanning.","tags":["scan"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"}}}},"x-quota-cost":1,"responses":{"202":{"description":"Scan queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AsyncScanAccepted"}}}},"400":{"description":"Empty address, unrecognised format, or chain/format mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"REST API access is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to create the job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scan/batch":{"post":{"operationId":"createBatchScan","summary":"Queue a batch scan","description":"Queues up to the plan's batch ceiling in one job and returns a job id. Each address costs 1 scan; the request is rejected up front when the remaining quota cannot cover the whole batch.","tags":["scan"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchScanRequest"}}}},"x-quota-cost":"1 per address","responses":{"200":{"description":"Batch queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchScanAccepted"}}}},"400":{"description":"No valid addresses, or the batch exceeds the plan ceiling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Batch scanning is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to create the job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scan/{job_id}":{"get":{"operationId":"getScanJobStatus","summary":"Get job status","description":"Status of a batch job or of an async single scan (both are jobs). Read-only — no quota charge.","tags":["scan"],"parameters":[{"name":"job_id","in":"path","required":true,"description":"Job id returned by the batch or async scan call.","schema":{"type":"string","format":"uuid"}}],"x-quota-cost":0,"responses":{"200":{"description":"Job status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatus"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`job_id` is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scan/{job_id}/results":{"get":{"operationId":"getScanJobResults","summary":"Get job results","description":"Per-address results for a job. Scores are returned on the same risk convention as the single scan and the response carries `score_semantics: \"risk_0to100\"`. Read-only — no quota charge.","tags":["scan"],"parameters":[{"name":"job_id","in":"path","required":true,"description":"Job id returned by the batch or async scan call.","schema":{"type":"string","format":"uuid"}}],"x-quota-cost":0,"responses":{"200":{"description":"Job results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResults"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`job_id` is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scans":{"get":{"operationId":"listScans","summary":"List recently recorded scans","description":"Recently recorded scans for the authenticated tenant, newest first. Read-only — no quota charge.\n\nCoverage: single scans (web console, synchronous `POST|GET /public/scan`, and asynchronous `POST /public/scan/async`) are recorded here. Large batch results are retrieved via the batch job endpoints (`GET /public/scan/{job_id}/results`), not from this feed.\n\nRetention: history follows the plan's `history_days` (Free 7, Starter 30, Pro 180, Business 365, Enterprise unlimited). Scans older than that window are not returned.","tags":["scan"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","required":false,"description":"Rows to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"x-quota-cost":0,"responses":{"200":{"description":"Recorded scans.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanListResponse"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/scans/{scan_id}":{"get":{"operationId":"getScan","summary":"Get one recorded scan","description":"Single recorded scan including its detections. Scans belonging to another tenant return 404. Read-only — no quota charge.\n\nRetention: the same `history_days` window as `GET /public/scans` applies (Free 7, Starter 30, Pro 180, Business 365, Enterprise unlimited). A scan older than the window returns 404, the same as an unknown id.","tags":["scan"],"parameters":[{"name":"scan_id","in":"path","required":true,"description":"Scan id from the list endpoint.","schema":{"type":"string","format":"uuid"}}],"x-quota-cost":0,"responses":{"200":{"description":"Recorded scan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanDetailResponse"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`scan_id` is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/presign/check":{"get":{"operationId":"presignCheck","summary":"Screening hot path (pre-deposit / pre-withdrawal / pre-signing)","description":"Low-latency verdict on a destination or spender before value moves. Built for per-transaction use: registry and scam-database lookups plus at most one RPC call, so it is orders of magnitude faster than a full scan.\n\n- **No monthly quota charge** — this is a lookup, not a scan pipeline run.\n- **Separate rate bucket** at 4x the plan's request rate (Pro 240, Business 480, Enterprise 1200 per minute), so screening traffic cannot starve scan / list calls, and vice versa.\n- Verdicts without calldata or a spender are cached ~120s; requests that carry either are always evaluated fresh.","tags":["screening"],"parameters":[{"name":"chain","in":"query","required":true,"description":"Chain id.","schema":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]}},{"name":"to","in":"query","required":true,"description":"Destination or spender address.","schema":{"type":"string"}},{"name":"value","in":"query","required":false,"description":"Transfer amount, for context.","schema":{"type":"string"}},{"name":"data","in":"query","required":false,"description":"Transaction calldata (0x…).","schema":{"type":"string"}},{"name":"calldata","in":"query","required":false,"description":"Alias of `data`.","schema":{"type":"string"}},{"name":"spender","in":"query","required":false,"description":"`approve()` spender to evaluate.","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"Caller-supplied operation hint.","schema":{"type":"string"}},{"name":"signer","in":"query","required":false,"description":"Signing address, for context.","schema":{"type":"string"}},{"name":"asset","in":"query","required":false,"description":"Asset identifier, for context.","schema":{"type":"string"}}],"x-quota-cost":0,"x-rate-bucket":"presign","responses":{"200":{"description":"Risk verdict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiskVerdict"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/usage":{"get":{"operationId":"getUsage","summary":"Current usage and plan limits","description":"Month-to-date scan usage plus the effective plan's rate and feature ceilings. Read-only — no quota charge. Poll this instead of hard-coding plan limits in client code.","tags":["usage"],"x-quota-cost":0,"responses":{"200":{"description":"Usage snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist":{"get":{"operationId":"listWatchlist","summary":"List watchlist items","tags":["monitoring"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","required":false,"description":"Rows to skip.","schema":{"type":"integer","default":0}}],"x-quota-cost":0,"responses":{"200":{"description":"Watchlist page.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WatchlistItem"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"addWatchlistItem","summary":"Add an address to the watchlist","description":"Monitored addresses are rescanned on the plan's interval and fire the risk.* webhook events on change. Plan ceilings are enforced server-side.","tags":["monitoring"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistAdd"}}}},"responses":{"200":{"description":"Item added.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid address format, or chain/format mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Watchlist capacity or Follow Mode is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/{item_id}":{"delete":{"operationId":"removeWatchlistItem","summary":"Remove a watchlist item","tags":["monitoring"],"parameters":[{"name":"item_id","in":"path","required":true,"description":"Watchlist item id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/{item_id}/rescan":{"post":{"operationId":"rescanWatchlistItem","summary":"Rescan a watchlist item now","description":"Runs the monitored scan immediately, including change detection and notifications. When Follow Mode is enabled and the plan allows it, graph expansion starts in the background and `follow_mode` reports its state.","tags":["monitoring"],"parameters":[{"name":"item_id","in":"path","required":true,"description":"Watchlist item id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rescan result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"success":{"type":"boolean"},"follow_mode":{"type":["string","null"],"enum":["running_in_background","plan_not_eligible","neo4j_unavailable","error",null]}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The scan failed upstream.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/{item_id}/follow":{"patch":{"operationId":"updateFollowMode","summary":"Enable / disable Follow Mode","description":"Follow Mode walks the transaction graph outward from the monitored address and surfaces newly discovered related addresses. Requires Pro or higher; the requested depth is clamped to the plan maximum.","tags":["monitoring"],"parameters":[{"name":"item_id","in":"path","required":true,"description":"Watchlist item id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowModeUpdate"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Follow Mode is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/{item_id}/discoveries":{"get":{"operationId":"listDiscoveries","summary":"List Follow Mode discoveries","tags":["monitoring"],"parameters":[{"name":"item_id","in":"path","required":true,"description":"Watchlist item id.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Discovery status filter.","schema":{"type":"string","default":"active"}}],"responses":{"200":{"description":"Discoveries.","content":{"application/json":{"schema":{"type":"object","properties":{"discoveries":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/discoveries/{discovery_id}/dismiss":{"post":{"operationId":"dismissDiscovery","summary":"Dismiss a discovery","tags":["monitoring"],"parameters":[{"name":"discovery_id","in":"path","required":true,"description":"Discovery id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Dismissed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/watchlist/discoveries/{discovery_id}/promote":{"post":{"operationId":"promoteDiscovery","summary":"Promote a discovery to the watchlist","tags":["monitoring"],"parameters":[{"name":"discovery_id","in":"path","required":true,"description":"Discovery id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Promoted.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The discovery cannot be promoted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/configs":{"get":{"operationId":"listWebhookConfigs","summary":"List webhook endpoints","tags":["monitoring"],"responses":{"200":{"description":"Configured endpoints.","content":{"application/json":{"schema":{"type":"object","properties":{"configs":{"type":"array","items":{"$ref":"#/components/schemas/WebhookConfig"}}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createWebhookConfig","summary":"Register a webhook endpoint","description":"Returns a signing secret **once**. Deliveries are signed with it, so store it before discarding the response. Plan ceilings on active endpoints are enforced server-side.","tags":["monitoring"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}}},"responses":{"200":{"description":"Endpoint created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Webhooks (or the endpoint ceiling is reached) is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to store the configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/configs/{config_id}":{"patch":{"operationId":"updateWebhookConfig","summary":"Update a webhook endpoint","description":"Partial update of URL, description, subscribed events, or active state.","tags":["monitoring"],"parameters":[{"name":"config_id","in":"path","required":true,"description":"Webhook configuration id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdate"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/WebhookConfig"}}}}}},"400":{"description":"No fields to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteWebhookConfig","summary":"Delete a webhook endpoint","tags":["monitoring"],"parameters":[{"name":"config_id","in":"path","required":true,"description":"Webhook configuration id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/test":{"post":{"operationId":"testWebhook","summary":"Send a test delivery","description":"Sends a signed sample `scan.completed` payload to the configured URL and reports the endpoint's response.","tags":["monitoring"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhook_config_id"],"properties":{"webhook_config_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Delivery attempted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"status_code":{"type":["integer","null"]},"error":{"type":["string","null"]}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List delivery attempts","description":"Delivery log for debugging endpoint failures, newest first.","tags":["monitoring"],"parameters":[{"name":"config_id","in":"query","required":false,"description":"Filter by endpoint.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Filter by delivery status.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows.","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Deliveries.","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/events":{"get":{"operationId":"listWebhookEventTypes","summary":"List subscribable event types","description":"The six event types a webhook endpoint can subscribe to. No authentication required.","tags":["monitoring"],"security":[],"responses":{"200":{"description":"Event catalog.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/WebhookEventType"},"description":{"type":"string"}}}}}}}}}}}},"/cases":{"get":{"operationId":"listCases","summary":"List investigation cases","tags":["investigation"],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter by case status.","schema":{"type":"string","enum":["open","in_review","escalated","closed","archived"]}},{"name":"limit","in":"query","required":false,"description":"Maximum rows.","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}}],"responses":{"200":{"description":"Cases.","content":{"application/json":{"schema":{"type":"object","properties":{"cases":{"type":"array","items":{"$ref":"#/components/schemas/Case"}},"count":{"type":"integer"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createCase","summary":"Create an investigation case","tags":["investigation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseCreate"}}}},"responses":{"200":{"description":"Case created.","content":{"application/json":{"schema":{"type":"object","properties":{"case":{"$ref":"#/components/schemas/Case"}}}}}},"400":{"description":"Invalid priority.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}":{"get":{"operationId":"getCase","summary":"Get a case with addresses and notes","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Case detail.","content":{"application/json":{"schema":{"type":"object","properties":{"case":{"$ref":"#/components/schemas/Case"},"addresses":{"type":"array","items":{"$ref":"#/components/schemas/CaseAddress"}},"notes":{"type":"array","items":{"$ref":"#/components/schemas/CaseNote"}}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateCase","summary":"Update case fields","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseUpdate"}}}},"responses":{"200":{"description":"Case updated.","content":{"application/json":{"schema":{"type":"object","properties":{"case":{"$ref":"#/components/schemas/Case"}}}}}},"400":{"description":"Invalid status/priority, or no fields to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteCase","summary":"Delete a case","description":"Cascades to the case's addresses and notes.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Case deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/addresses":{"post":{"operationId":"addCaseAddress","summary":"Add an address to a case","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseAddressAdd"}}}},"responses":{"200":{"description":"Address added.","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/CaseAddress"}}}}}},"400":{"description":"Invalid chain or role.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The address is already in this case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/addresses/{address_id}":{"patch":{"operationId":"updateCaseAddress","summary":"Update a case address","description":"Partial update of role, label, or investigator notes.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}},{"name":"address_id","in":"path","required":true,"description":"Case address id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseAddressUpdate"}}}},"responses":{"200":{"description":"Address updated.","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/CaseAddress"}}}}}},"400":{"description":"Invalid role, or no fields to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeCaseAddress","summary":"Remove an address from a case","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}},{"name":"address_id","in":"path","required":true,"description":"Case address id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Address removed.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/notes":{"post":{"operationId":"addCaseNote","summary":"Append a note to the case timeline","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseNoteCreate"}}}},"responses":{"200":{"description":"Note added.","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/CaseNote"}}}}}},"400":{"description":"Invalid note type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/notes/{note_id}":{"delete":{"operationId":"deleteCaseNote","summary":"Delete a case note","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}},{"name":"note_id","in":"path","required":true,"description":"Note id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Note deleted.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/bulk-scan":{"post":{"operationId":"startCaseBulkScan","summary":"Scan every address in a case","description":"Starts an asynchronous bulk scan and returns 202 with the selection breakdown. Only one bulk scan may run per case at a time (409 otherwise). The queue is capped at min(plan batch ceiling, remaining monthly quota) and truncated by priority: subjects first, then unscanned addresses, then increasing hop distance. Each address costs 1 scan. Poll `/cases/{case_id}/bulk-scan/status`.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkScanRequest"}}}},"x-quota-cost":"1 per scanned address","responses":{"202":{"description":"Bulk scan started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkScanAccepted"}}}},"400":{"description":"The case has no addresses, or every address was filtered out (the message itemises each reason).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A bulk scan is already running for this case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/bulk-scan/status":{"get":{"operationId":"getCaseBulkScanStatus","summary":"Poll bulk scan progress","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"x-quota-cost":0,"responses":{"200":{"description":"Progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkScanStatus"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/report.pdf":{"get":{"operationId":"getCaseReportPdf","summary":"Download the unified case report (PDF)","description":"One PDF covering every address in the case: case overview page followed by a per-address section with executive summary, detections, exposure, and counterparties.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"x-quota-cost":0,"responses":{"200":{"description":"PDF report.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The case has no addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/str-package":{"post":{"operationId":"startStrPackage","summary":"Generate a suspicious-transaction-report package (Enterprise)","description":"Starts asynchronous generation of the filing-support package: evidence PDF, editable report draft (.docx), and a 43-column transaction CSV. Returns 202 with a generation id; poll `/str-package/status`, then download the artifacts. Only one generation may run per case (409 otherwise). Enterprise plans only.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrPackageRequest"}}}},"x-min-plan":"enterprise","responses":{"202":{"description":"Generation started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrPackageAccepted"}}}},"400":{"description":"The case has no addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The report package is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A generation is already running for this case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to register the generation job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/str-package/status":{"get":{"operationId":"getStrPackageStatus","summary":"Poll report package generation (Enterprise)","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}}],"x-min-plan":"enterprise","x-quota-cost":0,"responses":{"200":{"description":"Generation status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrPackageStatus"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The report package is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/str-package/{generation_id}/download":{"get":{"operationId":"downloadStrPackage","summary":"Download the evidence PDF (Enterprise)","description":"Rebuilds the evidence PDF deterministically from the stored inputs — no rescan, no external calls, no quota charge — so the digest fixed at generation time is reproduced. `X-STR-Output-Hash` carries that digest and `X-STR-Degraded: 1` marks a simplified fallback rendering.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}},{"name":"generation_id","in":"path","required":true,"description":"Generation id from the 202 response.","schema":{"type":"string","format":"uuid"}}],"x-min-plan":"enterprise","x-quota-cost":0,"responses":{"200":{"description":"Evidence PDF.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-STR-Generation-Id":{"schema":{"type":"string"}},"X-STR-Output-Hash":{"schema":{"type":"string"}},"X-STR-Degraded":{"description":"Present and `1` when a simplified rendering was served.","schema":{"type":"string"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The report package is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The generation is still running, or it failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"This generation predates deterministic rebuild — regenerate it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cases/{case_id}/str-package/{generation_id}/draft.docx":{"get":{"operationId":"downloadStrPackageDraft","summary":"Download the editable report draft (Enterprise)","description":"The filing draft as .docx, for a compliance officer to edit and take responsibility for. The PDF is the fixed evidence record; this is the working document.","tags":["investigation"],"parameters":[{"name":"case_id","in":"path","required":true,"description":"Case id.","schema":{"type":"string","format":"uuid"}},{"name":"generation_id","in":"path","required":true,"description":"Generation id from the 202 response.","schema":{"type":"string","format":"uuid"}}],"x-min-plan":"enterprise","x-quota-cost":0,"responses":{"200":{"description":"Word document.","content":{"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The report package is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/risk-report/{address}":{"get":{"operationId":"getRiskReportPdf","summary":"Download a single-address risk assessment (PDF)","description":"Scan results, multi-hop exposure, and an AI-written executive summary in one PDF. Exposure is best-effort and bounded: if the graph analysis exceeds its budget the PDF is still produced without it.","tags":["investigation"],"parameters":[{"name":"address","in":"path","required":true,"description":"Address to report on.","schema":{"type":"string"}},{"name":"chain","in":"query","required":false,"description":"Chain hint. Auto-detected when omitted.","schema":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]}},{"name":"max_hops","in":"query","required":false,"description":"Exposure traversal depth.","schema":{"type":"integer","minimum":1,"maximum":5,"default":3}},{"name":"language","in":"query","required":false,"description":"Report language for the PDF template (headings, table headers). Detector descriptions and the AI narrative fall back to English for 'es'.","schema":{"type":"string","enum":["ja","en","es"],"default":"ja"}}],"x-quota-cost":0,"responses":{"200":{"description":"PDF report.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Empty address, or chain/format mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Scan or rendering failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/exposure/{address}":{"get":{"operationId":"getExposure","summary":"Multi-hop exposure analysis","description":"Counterparty exposure grouped into direct and indirect tiers, plus detected laundering patterns and total classified USD. Backed by the transaction graph, which is populated by scans — an address that has never been scanned returns empty buckets with an informational note. Read-only — no quota charge.","tags":["investigation"],"parameters":[{"name":"address","in":"path","required":true,"description":"Address to analyze.","schema":{"type":"string"}},{"name":"max_hops","in":"query","required":false,"description":"Traversal depth.","schema":{"type":"integer","minimum":1,"maximum":5,"default":3}},{"name":"limit","in":"query","required":false,"description":"Maximum paths per hop.","schema":{"type":"integer","minimum":10,"maximum":1000,"default":200}}],"x-quota-cost":0,"responses":{"200":{"description":"Exposure breakdown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Exposure"}}}},"400":{"description":"Address is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Graph analysis failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/exposure/{address}/terminals":{"get":{"operationId":"getExposureTerminals","summary":"Find classified endpoints from one origin (Business+)","description":"Give one origin address and get back every classified endpoint reachable within the hop limit, with the path to each — the cash-out side of a fund flow without needing to know the destination in advance. Business plan or higher. Read-only — no quota charge.","tags":["investigation"],"parameters":[{"name":"address","in":"path","required":true,"description":"Origin address.","schema":{"type":"string"}},{"name":"chain","in":"query","required":false,"description":"Chain hint, used to normalize the address.","schema":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]}},{"name":"max_hops","in":"query","required":false,"description":"Traversal depth.","schema":{"type":"integer","minimum":1,"default":3}},{"name":"categories","in":"query","required":false,"description":"Comma-separated filter: scam, mixing, exchange, bridge, sanctioned_entity.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum terminals returned.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"x-min-plan":"business","x-quota-cost":0,"responses":{"200":{"description":"Terminals with paths.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TerminalPaths"}}}},"400":{"description":"Address is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Terminal discovery is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Graph traversal failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/graph/flow-path":{"post":{"operationId":"findFlowPath","summary":"Directed fund-flow path between two addresses (Pro+)","description":"Finds a directed path from source to target; when none exists it retries target to source and marks the result `direction: \"reverse\"`. Pro plan or higher. Read-only — no quota charge.","tags":["graph"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowPathRequest"}}}},"x-min-plan":"pro","x-quota-cost":0,"responses":{"200":{"description":"Path result (check `found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowPathResponse"}}}},"400":{"description":"Invalid or identical addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Path finding is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/graph/relationship-check":{"post":{"operationId":"checkRelationship","summary":"Check whether two addresses are related (Enterprise)","description":"Returns the shortest path between two addresses, if any within the hop limit, plus their shared one-hop counterparties. With an EVM chain hint, a direct on-chain transaction check runs when the graph finds nothing. Enterprise plans only. Read-only — no quota charge.","tags":["graph"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelationshipCheckRequest"}}}},"x-min-plan":"enterprise","x-quota-cost":0,"responses":{"200":{"description":"Relationship result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelationshipCheckResponse"}}}},"400":{"description":"Invalid or identical addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Relationship check is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/graph/neighbors/{address}":{"get":{"operationId":"getGraphNeighbors","summary":"Graph neighborhood of an address","description":"One-hop neighborhood in node/edge form. No authentication required.","tags":["graph"],"security":[],"parameters":[{"name":"address","in":"path","required":true,"description":"Center address.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum neighbors.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Neighborhood.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphData"}}}},"400":{"description":"Invalid address format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/graph/expand/{address}":{"get":{"operationId":"expandGraphNode","summary":"Expand one node","description":"Incremental expansion for click-to-explore interfaces. No authentication required.","tags":["graph"],"security":[],"parameters":[{"name":"address","in":"path","required":true,"description":"Node to expand.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum neighbors.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Expanded neighbors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphData"}}}},"400":{"description":"Invalid address format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tx-query":{"post":{"operationId":"queryTokenTransfers","summary":"Token transfers for one address over a date range","description":"Every transfer of a single token contract touching a single address inside an explicit UTC window, plus a per-counterparty roll-up labelled from the known registry. Built for the reporting question a compliance team actually asks — *what did this customer do with this token last quarter* — which an address scan does not answer.\n\n- **Window is mandatory and bounded**: `start_date` and `end_date` are required, `end_date` must be later, and the span must not exceed 90 days. Longer periods are 400, not truncated.\n- **Result cap**: at most the 500 most recent transfers in the window are returned and aggregated. Narrow the window to page through more.\n- **Chains**: the 8 EVM chains plus Solana and TRON. This is one chain wider than `/contract-alert` — TRON is here because TronGrid's TRC-20 account feed is time-filterable.\n- Read-only — no monthly quota charge. Counts against the plan's per-minute request budget like any other call.","tags":["transfers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxQueryRequest"}}}},"x-quota-cost":0,"responses":{"200":{"description":"Transfers and counterparty roll-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxQueryResponse"}}}},"400":{"description":"Unsupported chain (the message lists the accepted set), an `end_date` at or before `start_date`, a window longer than 90 days, or an upstream failure resolving the dates to block heights.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing or malformed body field (FastAPI validation).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/contract-alert":{"post":{"operationId":"runContractAlert","summary":"Sweep a token contract for malicious counterparties","description":"Contract-wide inverse of `/tx-query`: instead of one address across a token, it sweeps every transfer of the token in the window and returns the addresses that match a malicious pattern — registry hits (`known_malicious`), `address_poisoning`, `relay_chain`, `rapid_distribution`, and `large_transfer`. Issuers use it as a standing check over their own token.\n\n- **Chains**: the 8 EVM chains plus Solana. **TRON is not accepted here** even though `/tx-query` accepts it — the contract-wide sweep has no TRON implementation, and the endpoint rejects the chain at the edge rather than failing deeper in.\n- Same mandatory, 90-day-bounded window and 500-transfer analysis cap as `/tx-query`; `total_transfers_analyzed` reports the input size.\n- Read-only — no monthly quota charge.","tags":["transfers"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractAlertRequest"}}}},"x-quota-cost":0,"responses":{"200":{"description":"Flagged addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractAlertResponse"}}}},"400":{"description":"Unsupported chain (`tron` lands here — the message lists the accepted set), an `end_date` at or before `start_date`, a window longer than 90 days, or an upstream failure resolving the dates to block heights.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing or malformed body field (FastAPI validation).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scan/tx":{"post":{"operationId":"scanTransaction","summary":"Scan a transaction by hash (Enterprise)","description":"Resolves the transaction, scans both counterparties, and returns the worse of the two risk levels with per-address detections. Enterprise plans only. Supported on all 12 chains: the 8 EVM chains plus Bitcoin, Solana, TRON and XRP. On TRON a TRC-20 transfer resolves to the token's Transfer event participants rather than the contract; on XRPL a Payment reports the delivered amount (never the requested `Amount`), and non-Payment types return the accounts involved with a note.","tags":["investigation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxScanRequest"}}}},"x-min-plan":"enterprise","responses":{"200":{"description":"Transaction assessment. `success: false` with `error` when the transaction cannot be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxScanResponse"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Transaction-hash scanning is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"That chain is not yet scannable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys":{"get":{"operationId":"listApiKeys","summary":"List API keys","description":"Only prefixes are returned — full keys are never retrievable.","tags":["keys"],"responses":{"200":{"description":"Keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createApiKey","summary":"Create an API key","description":"Returns the full key **once**; only its hash is stored. Maximum 5 active keys per account — revoke one before creating another.","tags":["keys"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"200":{"description":"Key created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"400":{"description":"The 5-key ceiling is reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API access is not available on the authenticated key's plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to store the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys/{key_id}":{"delete":{"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Deactivates the key. Propagation takes up to 5 minutes.","tags":["keys"],"parameters":[{"name":"key_id","in":"path","required":true,"description":"Key id from the list endpoint.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"401":{"description":"Missing, revoked, or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or owned by another tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (Retry-After + X-RateLimit-* headers) or the monthly scan quota is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key issued from the dashboard, formatted `tfk_` followed by 48 hexadecimal characters. `Authorization: Bearer tfk_…` is accepted as an equivalent."}},"schemas":{"Error":{"type":"object","description":"FastAPI error envelope.","properties":{"detail":{"type":"string","description":"Human-readable reason. Some 400s carry a per-cause breakdown."}}},"Detection":{"type":"object","description":"One fired detection rule.","required":["detector_id","detector_name","severity","description"],"properties":{"detector_id":{"type":"string","examples":["B2","C1","W7"]},"detector_name":{"type":"string","examples":["OFAC_SANCTIONED"]},"severity":{"type":"string","enum":["CRITICAL","HIGH","MEDIUM","LOW","INFO"]},"description":{"type":"string","description":"Localized per the request `lang` (en / ja)."},"details":{"type":"object","additionalProperties":true}}},"ScanRequest":{"type":"object","required":["address"],"properties":{"address":{"type":"string","description":"Blockchain address to scan."},"chain":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"],"description":"Chain hint. Omit to auto-detect from the address format. A hint that does not match the address format returns 400."},"lang":{"type":"string","enum":["en","ja"],"default":"en","description":"Language of the detection descriptions."}}},"ScanResponse":{"type":"object","description":"Flattened scan result. risk_score is 0-100 where **higher = riskier** (100 = most dangerous). Responses that carry historical or batch rows also include `score_semantics: \"risk_0to100\"` so integrators can assert the convention.","required":["success","address","chain","address_type","risk_level","risk_score","detection_count","detections"],"properties":{"success":{"type":"boolean"},"address":{"type":"string"},"chain":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]},"address_type":{"type":"string","description":"`wallet` or `token_mint` (Solana SPL mints).","examples":["wallet"]},"risk_level":{"type":"string","enum":["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN"]},"risk_score":{"type":"integer","minimum":0,"maximum":100,"description":"0 = clean, 100 = most dangerous."},"detection_count":{"type":"integer"},"detections":{"type":"array","items":{"$ref":"#/components/schemas/Detection"}},"metadata":{"type":"object","additionalProperties":true,"description":"Chain-specific facts present for this address only (balances, tx counts, token supply/authorities, is_sanctioned / is_mixer_contact / is_osint_flagged flags). Absent keys mean 'not applicable' or 'unknown'."},"ml_anomaly_score":{"type":["number","null"],"description":"0-1 ensemble anomaly score. null when ML did not run."},"scan_duration_ms":{"type":"integer"},"cached":{"type":"boolean","description":"true when served from the 30-minute result cache."}}},"AsyncScanAccepted":{"type":"object","description":"202 envelope for POST /public/scan/async. Poll `status_url` until `status` is `completed`, then read `results_url`.","required":["success","job_id","status","address","status_url","results_url"],"properties":{"success":{"type":"boolean"},"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","examples":["queued"]},"address":{"type":"string"},"chain":{"type":["string","null"],"enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp",null]},"status_url":{"type":"string","examples":["/api/v1/public/scan/{job_id}"]},"results_url":{"type":"string","examples":["/api/v1/public/scan/{job_id}/results"]},"message":{"type":"string"}}},"BatchScanRequest":{"type":"object","required":["addresses"],"properties":{"addresses":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"string"},"description":"Plain address strings — the chain is auto-detected per address. An unrecognisable address rejects the whole request with 400. Per-request ceiling is the plan's batch limit (Pro 100 / Business 200 / Enterprise 500)."},"notify_email":{"type":["string","null"],"format":"email","description":"Optional completion notification."},"include_ai_analysis":{"type":"boolean","default":false,"description":"Run the LLM narrative per result (Pro+, materially slower)."}}},"BatchScanAccepted":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"},"job_id":{"type":["string","null"],"format":"uuid"},"total_addresses":{"type":"integer"},"message":{"type":"string"}}},"JobStatus":{"type":"object","description":"Status of a batch job or of a single async scan (both are batch jobs).","required":["job_id","status"],"properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"Queue state, e.g. `pending`, `running`, `completed`, `failed`."},"total_addresses":{"type":"integer"},"completed_count":{"type":"integer"},"failed_count":{"type":"integer"},"risk_summary":{"type":"object","description":"Counters kept on the job row (already risk-oriented).","properties":{"critical":{"type":"integer"},"high":{"type":"integer"},"medium":{"type":"integer"},"low":{"type":"integer"}}}}},"JobResults":{"type":"object","required":["job_id","score_semantics","results"],"properties":{"job_id":{"type":"string","format":"uuid"},"score_semantics":{"type":"string","const":"risk_0to100","description":"Batch rows are returned on the same risk convention as the single scan (higher = riskier). Older clients that inverted the score themselves must stop doing so."},"results":{"type":"array","items":{"$ref":"#/components/schemas/BatchResultItem"}}}},"BatchResultItem":{"type":"object","properties":{"address":{"type":["string","null"]},"chain":{"type":["string","null"]},"status":{"type":"string","examples":["completed","pending","failed"]},"risk_level":{"type":["string","null"],"enum":["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN",null]},"risk_score":{"type":["integer","null"],"minimum":0,"maximum":100,"description":"null when the item has not completed."},"detection_count":{"type":"integer"},"detections":{"type":"array","items":{"$ref":"#/components/schemas/Detection"}},"ml_anomaly_score":{"type":["number","null"]},"metadata":{"type":"object","additionalProperties":true},"error_message":{"type":["string","null"]},"completed_at":{"type":["string","null"],"format":"date-time"}}},"ScanSummary":{"type":"object","description":"One recorded scan, on the public risk convention.","properties":{"scan_id":{"type":"string","format":"uuid"},"address":{"type":["string","null"]},"chain":{"type":["string","null"]},"address_type":{"type":"string"},"risk_level":{"type":["string","null"],"enum":["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN",null]},"risk_score":{"type":["integer","null"],"minimum":0,"maximum":100},"detection_count":{"type":"integer"},"ml_anomaly_score":{"type":["number","null"]},"scan_type":{"type":["string","null"]},"scan_duration_ms":{"type":"integer"},"metadata":{"type":"object","additionalProperties":true},"created_at":{"type":["string","null"],"format":"date-time"},"detections":{"type":"array","items":{"$ref":"#/components/schemas/Detection"},"description":"Detail responses only."}}},"ScanListResponse":{"type":"object","required":["score_semantics","scans"],"properties":{"score_semantics":{"type":"string","const":"risk_0to100"},"limit":{"type":"integer"},"offset":{"type":"integer"},"count":{"type":"integer"},"scans":{"type":"array","items":{"$ref":"#/components/schemas/ScanSummary"}}}},"ScanDetailResponse":{"type":"object","required":["score_semantics","scan"],"properties":{"score_semantics":{"type":"string","const":"risk_0to100"},"scan":{"$ref":"#/components/schemas/ScanSummary"}}},"RiskVerdict":{"type":"object","description":"Sign-time / pre-transfer verdict. Registry + scam-database lookups with at most one RPC call (approve spender code check).","required":["level","verdict","score"],"properties":{"level":{"type":"string","enum":["critical","high","medium","low","unknown"],"description":"`unknown` is also returned for an unsupported chain id."},"verdict":{"type":"string","enum":["ok","caution","danger"],"description":"Collapsed banner state for wallet / teller UIs."},"score":{"type":"integer","minimum":0,"maximum":100,"description":"Higher = riskier."},"reasons":{"type":"array","items":{"type":"string"}},"signals":{"type":"object","additionalProperties":true,"description":"Machine-readable signals behind the verdict."},"confidence":{"type":"number","minimum":0,"maximum":1},"chain":{"type":"string"},"to":{"type":"string"},"latency_ms":{"type":"integer"},"cached":{"type":"boolean","description":"Verdicts without calldata / spender are cached ~120s. Requests that carry calldata or a spender are always evaluated fresh."}}},"Usage":{"type":"object","description":"Current-month usage and the effective plan's ceilings.","properties":{"plan_id":{"type":"string","examples":["enterprise"]},"scans_per_month":{"type":"integer","description":"-1 means unlimited (Enterprise)."},"scans_used":{"type":"integer"},"scans_remaining":{"type":"integer","description":"-1 means unlimited."},"topup_remaining":{"type":"integer","description":"Purchased top-up scans left."},"period":{"type":"string","examples":["2026-08"],"description":"UTC year-month."},"period_reset_at":{"type":["string","null"],"format":"date-time","description":"First instant of the next UTC month."},"api_rate_limit_per_min":{"type":"integer"},"presign_rate_limit_per_min":{"type":"integer","description":"Separate bucket = api_rate_limit_per_min x 4."},"batch_limit":{"type":"integer"},"watchlist_limit":{"type":"integer"},"webhook_limit":{"type":"integer"}}},"WatchlistItem":{"type":"object","description":"Watchlist row as stored (fields are additive over time).","additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"token_address":{"type":"string"},"chain":{"type":"string"},"label":{"type":["string","null"]},"monitor_type":{"type":"string","enum":["address","token_contract"]},"follow_mode_enabled":{"type":"boolean"},"follow_depth":{"type":"integer"},"last_risk_level":{"type":["string","null"]},"last_scanned_at":{"type":["string","null"],"format":"date-time"}}},"WatchlistAdd":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"chain":{"type":["string","null"],"enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp",null],"description":"Auto-detected when omitted. Must match the address format."},"label":{"type":["string","null"]},"notify_email":{"type":["string","null"],"format":"email"},"follow_mode_enabled":{"type":["boolean","null"],"description":"Requires Pro+. Depth is clamped to the plan maximum."},"follow_depth":{"type":["integer","null"],"minimum":1},"monitor_type":{"type":"string","enum":["address","token_contract"],"default":"address","description":"`token_contract` is EVM + Solana + TRON only."}}},"FollowModeUpdate":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"depth":{"type":"integer","default":1,"description":"Clamped server-side to the plan maximum (Pro 2, Business/Enterprise 3). Disabling is allowed on any plan."}}},"WebhookConfig":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"webhook_url":{"type":"string","format":"uri"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"WebhookEventType":{"type":"string","enum":["risk.changed","risk.high_detected","risk.critical_detected","scan.completed","batch.completed","watchlist.alert"]},"WebhookCreate":{"type":"object","required":["webhook_url"],"properties":{"webhook_url":{"type":"string","format":"uri"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"default":["risk.changed","risk.high_detected"]}}},"WebhookCreated":{"type":"object","description":"The signing secret is returned once at creation and never again — store it before discarding the response.","properties":{"config":{"$ref":"#/components/schemas/WebhookConfig"},"secret":{"type":"string"},"message":{"type":"string"}}},"WebhookUpdate":{"type":"object","description":"Partial update — at least one field is required.","properties":{"webhook_url":{"type":["string","null"],"format":"uri"},"description":{"type":["string","null"]},"events":{"type":["array","null"],"items":{"$ref":"#/components/schemas/WebhookEventType"}},"is_active":{"type":["boolean","null"]}}},"Case":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["open","in_review","escalated","closed","archived"]},"priority":{"type":"string","enum":["low","normal","high","urgent"]},"tags":{"type":"array","items":{"type":"string"}},"address_count":{"type":"integer","description":"List responses only."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CaseCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":["string","null"]},"priority":{"type":"string","enum":["low","normal","high","urgent"],"default":"normal"},"tags":{"type":"array","items":{"type":"string"}}}},"CaseUpdate":{"type":"object","description":"Partial update. Setting `status` to `closed` / `archived` also stamps `closed_at`. An empty body returns 400.","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":["string","null"]},"status":{"type":"string","enum":["open","in_review","escalated","closed","archived"]},"priority":{"type":"string","enum":["low","normal","high","urgent"]},"tags":{"type":"array","items":{"type":"string"}}}},"CaseAddress":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"address":{"type":"string"},"chain":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]},"role":{"type":"string","enum":["subject","witness","counterparty","cluster_member"]},"label":{"type":["string","null"]},"notes":{"type":["string","null"]},"hop_distance":{"type":["integer","null"]},"is_terminal":{"type":["boolean","null"]},"is_infrastructure":{"type":["boolean","null"],"description":"Exchange / DEX router / bridge endpoints. Excluded from bulk scans by default because they reliably exceed the per-scan timeout."},"last_risk_level":{"type":["string","null"]},"last_risk_score":{"type":["integer","null"]},"last_scanned_at":{"type":["string","null"],"format":"date-time"}}},"CaseAddressAdd":{"type":"object","required":["address","chain"],"properties":{"address":{"type":"string","minLength":1},"chain":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"]},"role":{"type":"string","enum":["subject","witness","counterparty","cluster_member"],"default":"subject"},"label":{"type":["string","null"]},"notes":{"type":["string","null"]}}},"CaseAddressUpdate":{"type":"object","description":"Partial update of role / label / notes. Empty body returns 400.","properties":{"role":{"type":"string","enum":["subject","witness","counterparty","cluster_member"]},"label":{"type":["string","null"]},"notes":{"type":["string","null"]}}},"CaseNote":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"body":{"type":"string"},"note_type":{"type":"string","enum":["comment","finding","action_taken","status_change","scan_summary"]},"related_address":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}}},"CaseNoteCreate":{"type":"object","required":["body"],"properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"note_type":{"type":"string","enum":["comment","finding","action_taken","status_change","scan_summary"],"default":"comment"},"related_address":{"type":["string","null"]}}},"BulkScanRequest":{"type":"object","description":"All fields optional; the body itself may be omitted.","properties":{"only_unscanned":{"type":"boolean","default":false},"include_infrastructure":{"type":"boolean","default":false,"description":"Include exchange / DEX-router / bridge endpoints. Off by default — they time out per scan. Case subjects are always included."},"lang":{"type":"string","enum":["ja","en"],"default":"ja","description":"Language of the completion note persisted to the case timeline (it cannot be translated afterwards)."}}},"BulkScanAccepted":{"type":"object","properties":{"status":{"type":"string","const":"started"},"case_id":{"type":"string","format":"uuid"},"total":{"type":"integer","description":"Addresses actually queued."},"skipped":{"type":"integer"},"skipped_scanned":{"type":"integer"},"skipped_over_cap":{"type":"integer"},"skipped_infrastructure":{"type":"integer"},"cap":{"type":"integer","description":"min(plan batch limit, remaining monthly quota)."},"quota_charged":{"type":"integer","description":"Upper bound — only successful scans are actually charged."},"quota_unlimited":{"type":"boolean"},"quota_remaining":{"type":"integer"},"criteria":{"type":["string","null"],"description":"Selection criteria text, present only when the cap truncated."},"lang":{"type":"string"}}},"BulkScanStatus":{"type":"object","additionalProperties":true,"properties":{"case_id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"`idle` / `running` / `completed` / `failed`, plus `stale` when a job lost its lease (replica restart) — treat `stale` as terminal and retry."},"total":{"type":"integer"},"done":{"type":"integer"},"errors":{"type":"integer"},"quota_charged":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]}}},"StrPackageRequest":{"type":"object","description":"All fields optional; the body itself may be omitted.","properties":{"max_addresses":{"type":["integer","null"],"minimum":1,"maximum":50,"description":"Addresses that get a rescan plus a detailed chapter (default 20). The transaction CSV always covers every address in the case."},"language":{"type":"string","enum":["ja","en","es"],"default":"ja","description":"Package language and format. 'ja' follows the JAFIC filing format (43-column CSV, JST timestamps); 'en' and 'es' produce the international evidence format (13-column CSV, UTC timestamps) — 'es' is intended as annex evidence for Spanish-language regulators (e.g. UIF-Perú). Fixed at generation time; downloads are rebuilt in the stored language."}}},"StrPackageAccepted":{"type":"object","properties":{"status":{"type":"string","const":"started"},"case_id":{"type":"string","format":"uuid"},"generation_id":{"type":"string","format":"uuid"},"detail_cap":{"type":"integer"},"address_count":{"type":"integer"}}},"StrPackageStatus":{"type":"object","properties":{"case_id":{"type":"string","format":"uuid"},"generation_id":{"type":["string","null"],"format":"uuid"},"status":{"type":"string","description":"`idle` (never generated) / `pending` / `running` / `completed` / `failed`, plus `stale` when the job lost its lease."},"error_message":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"output_hash":{"type":["string","null"],"description":"Digest of the evidence PDF fixed at generation time. Downloads rebuild the same bytes deterministically, so the hash is reproducible."}}},"Exposure":{"type":"object","additionalProperties":true,"description":"Multi-hop exposure grouped by directness and risk tier. Returns empty buckets with an informational note when the address is not yet in the graph.","properties":{"direct":{"type":"array","items":{"type":"object","additionalProperties":true}},"indirect_high":{"type":"array","items":{"type":"object","additionalProperties":true}},"indirect_medium":{"type":"array","items":{"type":"object","additionalProperties":true}},"indirect_low":{"type":"array","items":{"type":"object","additionalProperties":true}},"money_laundering_patterns":{"type":"array","items":{"type":"object","additionalProperties":true}},"total_exposure_usd":{"type":"number"},"meta":{"type":"object","additionalProperties":true}}},"TerminalPaths":{"type":"object","description":"Classified endpoints reachable from a single origin, with the path to each. Same path/node shape as the flow-path response.","properties":{"success":{"type":"boolean"},"address":{"type":"string"},"chain":{"type":["string","null"]},"categories":{"type":["array","null"],"items":{"type":"string"}},"terminals":{"type":"array","items":{"type":"object","additionalProperties":true}},"searched_hops":{"type":"integer"},"truncated":{"type":"boolean"},"not_ingested":{"type":"boolean","description":"true when the address has never been scanned into the graph."},"message":{"type":["string","null"]}}},"GraphData":{"type":"object","description":"Cytoscape-compatible node/edge payload. `available: false` with empty arrays when the graph database is unreachable.","properties":{"nodes":{"type":"array","items":{"type":"object","additionalProperties":true}},"edges":{"type":"array","items":{"type":"object","additionalProperties":true}},"available":{"type":"boolean"},"center":{"type":"string"},"error":{"type":"string"}}},"FlowPathRequest":{"type":"object","required":["source","target"],"properties":{"source":{"type":"string"},"target":{"type":"string"},"max_hops":{"type":"integer","minimum":1,"maximum":8,"default":6}}},"FlowPathResponse":{"type":"object","properties":{"success":{"type":"boolean"},"source":{"type":"string"},"target":{"type":"string"},"found":{"type":"boolean"},"direction":{"type":["string","null"],"enum":["forward","reverse",null],"description":"`reverse` means no source→target path existed but target→source did."},"hops":{"type":["integer","null"]},"path":{"type":"array","items":{"type":"object","properties":{"from_address":{"type":"string"},"to_address":{"type":"string"},"rel_type":{"type":"string"},"token":{"type":["string","null"]},"tx_sig":{"type":["string","null"]},"bridge":{"type":["string","null"]},"target_chain":{"type":["string","null"]},"chain":{"type":["string","null"]}}}},"nodes":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"chain":{"type":["string","null"]},"type":{"type":["string","null"]},"label":{"type":["string","null"]}}}},"note":{"type":["string","null"]},"error":{"type":["string","null"]}}},"RelationshipCheckRequest":{"type":"object","required":["address_a","address_b"],"properties":{"address_a":{"type":"string"},"address_b":{"type":"string"},"chain":{"type":["string","null"],"enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp",null],"description":"EVM hints also enable a direct on-chain transaction fallback when the graph has no path."},"max_hops":{"type":"integer","minimum":1,"maximum":6,"default":4}}},"RelationshipCheckResponse":{"type":"object","properties":{"success":{"type":"boolean"},"address_a":{"type":"string"},"address_b":{"type":"string"},"related":{"type":"boolean"},"shortest_path_hops":{"type":["integer","null"]},"shortest_path":{"type":"array","items":{"type":"object","properties":{"from_address":{"type":"string"},"to_address":{"type":"string"},"direction":{"type":"string","enum":["->","<-"]},"token":{"type":["string","null"]}}}},"common_counterparties":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"label":{"type":["string","null"]},"category":{"type":["string","null"]},"direction_a":{"type":["string","null"]},"direction_b":{"type":["string","null"]}}}},"note":{"type":["string","null"]},"error":{"type":["string","null"]}}},"TxQueryRequest":{"type":"object","description":"Every field except `lang` is required — there is no 'recent transfers' default. The window is bounded server-side at 90 days.","required":["address","token_contract","chain","start_date","end_date"],"properties":{"address":{"type":"string","description":"Target wallet address."},"token_contract":{"type":"string","description":"ERC-20 / SPL mint / TRC-20 token contract address. Results are restricted to transfers of this one token."},"chain":{"type":"string","enum":["ethereum","polygon","avalanche","bsc","base","arbitrum","optimism","kaia","solana","tron"],"description":"Chain id. XRP and Bitcoin are not accepted by either mode: XRPL issued currencies are (currency, issuer) pairs rather than contracts, and Bitcoin has no token layer, so the `token_contract` filter has no counterpart on either. Use the address-scan surfaces for those chains."},"start_date":{"type":"string","format":"date-time","description":"Window start (UTC). Converted to a block height per chain."},"end_date":{"type":"string","format":"date-time","description":"Window end (UTC). Must be after `start_date`, and the span must not exceed 90 days."},"lang":{"type":"string","enum":["en","ja"],"default":"en"}}},"TokenTransfer":{"type":"object","description":"One token transfer touching the queried address.","required":["tx_hash","timestamp","from_address","to_address","amount","direction","counterparty"],"properties":{"tx_hash":{"type":"string"},"block_number":{"type":"integer","default":0},"timestamp":{"type":"string","format":"date-time"},"from_address":{"type":"string"},"to_address":{"type":"string"},"amount":{"type":"number","description":"Decimal-adjusted token amount."},"token_symbol":{"type":"string"},"token_decimals":{"type":"integer","default":18},"direction":{"type":"string","enum":["sent","received"],"description":"Relative to the queried `address`."},"counterparty":{"type":"string","description":"The other party's address."}}},"CounterpartySummary":{"type":"object","description":"Per-counterparty aggregate over the queried window, labelled from the known registry where a match exists.","required":["address"],"properties":{"address":{"type":"string"},"label":{"type":"string","default":"Unknown"},"category":{"type":"string","default":"unknown","description":"Registry category (exchange, bridge, mixer, dex_router, …) or `unknown` when unlabelled."},"sent_count":{"type":"integer","default":0},"received_count":{"type":"integer","default":0},"sent_amount":{"type":"number","default":0},"received_amount":{"type":"number","default":0},"net_amount":{"type":"number","default":0,"description":"received_amount - sent_amount, from the counterparty's side."}}},"TxQueryResponse":{"type":"object","description":"Transfers plus the counterparty roll-up. A failed query is surfaced as 400 with `detail`, so a 200 always carries `success: true` and a null `error`. Transfers are capped at the 500 most recent in the window; `total_transfers` is the count actually returned.","required":["success"],"properties":{"success":{"type":"boolean"},"chain":{"type":"string","enum":["ethereum","polygon","avalanche","bsc","base","arbitrum","optimism","kaia","solana","tron"]},"address":{"type":"string"},"token_contract":{"type":"string"},"token_symbol":{"type":"string"},"start_date":{"type":["string","null"],"format":"date-time"},"end_date":{"type":["string","null"],"format":"date-time"},"total_transfers":{"type":"integer"},"transfers":{"type":"array","items":{"$ref":"#/components/schemas/TokenTransfer"}},"counterparties":{"type":"array","items":{"$ref":"#/components/schemas/CounterpartySummary"}},"query_duration_ms":{"type":"integer"},"error":{"type":["string","null"]}}},"ContractAlertRequest":{"type":"object","description":"Contract-wide sweep — no `address`. Same 90-day window bound as the transfer query.","required":["token_contract","chain","start_date","end_date"],"properties":{"token_contract":{"type":"string","description":"Token contract / mint address to sweep."},"chain":{"type":"string","enum":["ethereum","polygon","avalanche","bsc","base","arbitrum","optimism","kaia","solana"],"description":"Chain id. TRON is accepted by /tx-query but **not** here. XRP and Bitcoin are not accepted by either mode: XRPL issued currencies are (currency, issuer) pairs rather than contracts, and Bitcoin has no token layer, so the `token_contract` filter has no counterpart on either. Use the address-scan surfaces for those chains."},"start_date":{"type":"string","format":"date-time"},"end_date":{"type":"string","format":"date-time","description":"Must be after `start_date`; the span must not exceed 90 days."},"lang":{"type":"string","enum":["en","ja"],"default":"en"}}},"FlaggedAddress":{"type":"object","description":"One address the sweep flagged, with the pattern that fired.","required":["address","pattern_type","severity","reason"],"properties":{"address":{"type":"string"},"pattern_type":{"type":"string","enum":["known_malicious","address_poisoning","relay_chain","rapid_distribution","large_transfer"]},"severity":{"type":"string","enum":["CRITICAL","HIGH","MEDIUM"]},"reason":{"type":"string","description":"Human-readable detection reason, localized per `lang`."},"tx_count":{"type":"integer","default":0},"total_amount":{"type":"number","default":0},"sample_tx_hash":{"type":"string","description":"One representative transaction, for evidence linking."}}},"ContractAlertResponse":{"type":"object","description":"Sweep result. As with the transfer query, a failure is surfaced as 400 with `detail`, so a 200 always carries `success: true`.","required":["success"],"properties":{"success":{"type":"boolean"},"chain":{"type":"string","enum":["ethereum","polygon","avalanche","bsc","base","arbitrum","optimism","kaia","solana"]},"token_contract":{"type":"string"},"token_symbol":{"type":"string"},"start_date":{"type":["string","null"],"format":"date-time"},"end_date":{"type":["string","null"],"format":"date-time"},"total_transfers_analyzed":{"type":"integer","description":"Transfers examined in the window (the analysis input, not a match count). Capped at 500."},"flagged_addresses":{"type":"array","items":{"$ref":"#/components/schemas/FlaggedAddress"}},"query_duration_ms":{"type":"integer"},"error":{"type":["string","null"]}}},"TxScanRequest":{"type":"object","required":["tx_hash","chain"],"properties":{"tx_hash":{"type":"string","description":"Transaction hash / signature. `0x` + 64 hex on EVM chains; bare 64 hex on Bitcoin, TRON and XRP; 87-88 char base58 on Solana."},"chain":{"type":"string","enum":["bitcoin","ethereum","polygon","bsc","base","arbitrum","optimism","avalanche","kaia","solana","tron","xrp"],"description":"All 12 chains are resolvable by transaction hash."},"lang":{"type":"string","enum":["en","ja"],"default":"en"}}},"TxScanResponse":{"type":"object","description":"Looks the transaction up, scans both counterparties, and returns the worse of the two risk levels with per-address breakdowns. Scores here follow the internal scan model (see `risk_level` for the unambiguous signal).","properties":{"success":{"type":"boolean"},"chain":{"type":["string","null"]},"tx_hash":{"type":["string","null"]},"from_address":{"type":["string","null"]},"to_address":{"type":["string","null"]},"value_native":{"type":["number","null"]},"block_number":{"type":["integer","null"]},"timestamp":{"type":["integer","null"]},"note":{"type":["string","null"]},"overall_risk_level":{"type":["string","null"],"enum":["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN",null]},"overall_risk_score":{"type":["integer","null"]},"addresses":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"role":{"type":"string","enum":["from","to"]},"risk_level":{"type":"string","enum":["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN"]},"risk_score":{"type":"integer"},"detection_count":{"type":"integer"},"detections":{"type":"array","items":{"$ref":"#/components/schemas/Detection"}}}}},"error":{"type":["string","null"]}}},"ApiKeyCreate":{"type":"object","properties":{"name":{"type":["string","null"],"description":"Defaults to \"Default\"."}}},"ApiKeyCreated":{"type":"object","description":"The full key is shown exactly once — it is stored only as a hash.","properties":{"key":{"type":"string","examples":["tfk_…"]},"prefix":{"type":"string","description":"`tfk_` plus 8 characters."},"name":{"type":"string"},"message":{"type":"string"}}},"ApiKeyList":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key_prefix":{"type":"string"},"name":{"type":"string"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}}}}}}}}}