API Reference

Integrate ChainAnalyzer via REST API & ScamDB API

REST API access requires a Pro plan or higher. ScamDB read endpoints are free.

Authentication

REST API requests require a tfk_ prefixed API key. Create one from Dashboard > Settings > API Keys (up to 5 per account; the full key is shown once at creation).

Send via X-API-Key header:

curl -H "X-API-Key: tfk_your_key_here" \
     https://chain-analyzer.com/api/v1/public/scan

Base URL

https://chain-analyzer.com/api/v1

OpenAPI Specification

Machine-readable definition of every endpoint (OpenAPI 3.1). Load it straight into Postman, Insomnia, or a client generator.

curl https://chain-analyzer.com/openapi-enterprise.json

Score convention

risk_score runs 0 (safe) to 100 (critical) — higher means riskier. The same convention applies to single scans, batch results, scan history, and webhook payloads. History and batch responses also carry score_semantics: "risk_0to100" so clients can assert it.

Chain identifiers are bitcoin / ethereum / polygon / bsc / base / arbitrum / optimism / avalanche / kaia / solana / tron / xrp. BNB Smart Chain is bsc (bnb returns 400). Omit the hint to auto-detect from the address format.

PATCH is supported (partial updates for webhook configs, Follow Mode, cases, and case addresses).

Quickstart: VASP integration in 15 minutes

The production core loop in four steps, from deciding on a deposit to keeping the address under watch. Every example is copy-pasteable curl — swap tfk_your_key_here for your own key.

Step 1: Screen an address before accepting a deposit

Use two lanes. On the hot path, GET /public/presign/check returns a verdict immediately; fall through to the full POST /public/scan only when the verdict is not ok. The example is an incoming USDT deposit on TRON (token_contract = TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t).

1. Fast lane — safe to call on every transaction (0 quota, its own rate bucket, ~120s cache)

curl -H "X-API-Key: tfk_your_key_here" \
  "https://chain-analyzer.com/api/v1/public/presign/check?chain=tron&to=TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX"
{
  "level": "low",
  "verdict": "ok",
  "score": 5,
  "reasons": [],
  "signals": { "sanctioned": false, "scamdb_hit": false },
  "confidence": 0.9,
  "chain": "tron",
  "to": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
  "latency_ms": 41,
  "cached": false
}

verdict collapses to ok / caution / danger and maps straight onto a banner state. level is critical / high / medium / low / unknown (an unsupported chain also returns unknown), and score runs 0-100 with higher meaning riskier.

2. Full scan — only when the verdict is not ok (costs 1 scan)

curl -X POST https://chain-analyzer.com/api/v1/public/scan \
  -H "X-API-Key: tfk_your_key_here" -H "Content-Type: application/json" \
  -d '{"address":"TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX","chain":"tron"}'
{
  "success": true,
  "address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
  "chain": "tron",
  "address_type": "wallet",
  "risk_level": "LOW",
  "risk_score": 8,
  "detection_count": 0,
  "detections": [],
  "metadata": { "is_sanctioned": false, "total_transactions": 1204 },
  "ml_anomaly_score": 0.1147,
  "scan_duration_ms": 6321,
  "cached": false
}

Step 2: Poll an async scan for heavy addresses

Bitcoin addresses with large UTXO sets and very high-frequency EVM accounts can outlast the synchronous HTTP deadline. Take the 202 and its job id, poll status_url until status is completed, then read results_url. It costs the same 1 scan as the synchronous call.

curl -X POST https://chain-analyzer.com/api/v1/public/scan/async \
  -H "X-API-Key: tfk_your_key_here" -H "Content-Type: application/json" \
  -d '{"address":"347QFbejDBdMZFTxpmn6evvvqyXiqZTCd7","chain":"bitcoin"}'
{
  "success": true,
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "address": "347QFbejDBdMZFTxpmn6evvvqyXiqZTCd7",
  "chain": "bitcoin",
  "status_url": "/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000",
  "results_url": "/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000/results",
  "message": "Scan queued. Poll status_url until status is 'completed'."
}

Poll, then fetch the results

curl -H "X-API-Key: tfk_your_key_here" \
  https://chain-analyzer.com/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000
curl -H "X-API-Key: tfk_your_key_here" \
  https://chain-analyzer.com/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000/results

Step 3: Put the address under continuous monitoring

Adding a screened address to the watchlist enrolls it in periodic rescans and fires a webhook the moment its risk level moves. The initial scan runs asynchronously, so the response comes back with initial_scan.status: "pending" and the row is updated once it finishes.

curl -X POST https://chain-analyzer.com/api/v1/watchlist \
  -H "X-API-Key: tfk_your_key_here" -H "Content-Type: application/json" \
  -d '{"address":"TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX","chain":"tron","label":"deposit-12345"}'
{
  "success": true,
  "item": {
    "id": "8f14e45f-ceea-467a-9a1c-1f0d3b6a2f77",
    "token_address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
    "chain": "tron",
    "label": "deposit-12345",
    "monitor_type": "address",
    "follow_mode_enabled": false,
    "follow_depth": 1,
    "last_risk_level": null,
    "last_scanned_at": null
  },
  "initial_scan": {
    "status": "pending",
    "risk_level": "UNKNOWN",
    "risk_score": 0,
    "detections": []
  }
}

Register a webhook endpoint

curl -X POST https://chain-analyzer.com/api/v1/webhooks/configs \
  -H "X-API-Key: tfk_your_key_here" -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://ops.example.com/hooks/chainanalyzer",
       "description":"AML alerts",
       "events":["risk.changed","risk.critical_detected"]}'
{
  "config": {
    "id": "3d1f0c6e-5a2b-4f19-8f7a-0c9d2e6b41aa",
    "webhook_url": "https://ops.example.com/hooks/chainanalyzer",
    "description": "AML alerts",
    "events": ["risk.changed", "risk.critical_detected"],
    "is_active": true,
    "created_at": "2026-08-14T02:11:44Z"
  },
  "secret": "b7c19a2f…8e04d1",
  "message": "Save this secret securely. It won't be shown again."
}

The secret is returned exactly once. Deliveries carry X-ChainAnalyzer-Signature: sha256=<HMAC-SHA256(secret, raw request body)> — verify against the raw body bytes, not a re-serialized object.

The actual delivery payload for risk.critical_detected:

{
  "event": "risk.critical_detected",
  "timestamp": "2026-08-14T03:27:19.482913",
  "data": {
    "address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
    "chain": "tron",
    "previous_risk_level": "LOW",
    "risk_level": "CRITICAL",
    "risk_score": 100,
    "detections": [
      {
        "detector_id": "B2",
        "detector_name": "OFAC_SANCTIONED",
        "severity": "CRITICAL",
        "description": "Address is on OFAC SDN sanctions list"
      }
    ],
    "detail_url": "https://chain-analyzer.com/scan?address=TAUN6Fwrnwwm…&chain=tron",
    "score_semantics": "risk_0to100"
  }
}

risk.critical_detected and risk.high_detected fire as separate events from risk.changed, at the moment a watched address newly crosses that level (limited to watchlist items so you are not notified twice). The data body is identical across all three. Only when webhook_url points at hooks.slack.com is the payload converted to Slack blocks instead of this shape.

Step 4: Check your remaining quota

Read this instead of hard-coding plan limits in client code. It is read-only and costs no quota. A value of -1 means unlimited.

curl -H "X-API-Key: tfk_your_key_here" \
  https://chain-analyzer.com/api/v1/public/usage
{
  "plan_id": "enterprise",
  "scans_per_month": -1,
  "scans_used": 8412,
  "scans_remaining": -1,
  "topup_remaining": 0,
  "period": "2026-08",
  "period_reset_at": "2026-09-01T00:00:00Z",
  "api_rate_limit_per_min": 300,
  "presign_rate_limit_per_min": 1200,
  "batch_limit": 500,
  "watchlist_limit": -1,
  "webhook_limit": -1
}

Client example (Python)

A minimal screen-then-decide loop with requests: fast lane first, full scan only when needed, then the decision.

import requests

BASE = "https://chain-analyzer.com/api/v1"
HEADERS = {"X-API-Key": "tfk_your_key_here"}


def screen(address: str, chain: str) -> dict:
    """Fast lane first; escalate to a full scan only when it is not clean."""
    r = requests.get(
        f"{BASE}/public/presign/check",
        params={"chain": chain, "to": address},
        headers=HEADERS,
        timeout=10,
    )
    r.raise_for_status()
    verdict = r.json()

    # "ok" means registry + scam-database lookups found nothing. Accept and stop:
    # this call costs no monthly quota, so it is safe on every transaction.
    if verdict["verdict"] == "ok":
        return {"decision": "accept", "risk_score": verdict["score"]}

    # Not clean -> spend 1 scan on the full detector pipeline for the audit trail.
    r = requests.post(
        f"{BASE}/public/scan",
        json={"address": address, "chain": chain},
        headers=HEADERS,
        timeout=120,
    )
    r.raise_for_status()
    scan = r.json()

    # risk_score is 0-100, higher = riskier. CRITICAL always blocks.
    decision = "block" if scan["risk_level"] in ("CRITICAL", "HIGH") else "review"
    return {
        "decision": decision,
        "risk_score": scan["risk_score"],
        "detections": [d["detector_name"] for d in scan["detections"]],
    }


print(screen("TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX", "tron"))

Screening hot path (pre-deposit / pre-withdrawal / pre-signing)

A low-latency risk verdict for deciding whether to accept a deposit, release a withdrawal, or warn before a wallet signs. Built for the VASP hot path.

  • Low latency: registry and ScamDB lookups, with at most one external RPC call (the approve spender check).
  • No monthly quota charge: it is designed to be called on every transaction, so it is not metered against scans.
  • Its own rate budget: a separate bucket at 4x your plan rate (Pro 240 / Business 480 / Enterprise 1200 per minute), so screening traffic never starves scans or batch jobs.
  • Verdicts without calldata or a spender are cached ~120s; requests carrying either are always evaluated fresh.
MethodPathAuthDescription
GET/public/presign/checktfk_Pre-signing risk verdict for a destination / spender (API-key form)

Main parameters: chain (required), to (required — destination or spender), value, data / calldata, spender, kind, signer, asset.

curl -H "X-API-Key: tfk_your_key_here" \
  "https://chain-analyzer.com/api/v1/public/presign/check?chain=ethereum&to=0x1234..."

Response

{
  "level": "critical",
  "verdict": "danger",
  "score": 100,
  "reasons": ["Address is on the OFAC SDN sanctions list"],
  "signals": { "sanctioned": true },
  "confidence": 1.0,
  "chain": "ethereum",
  "to": "0x1234...",
  "latency_ms": 12,
  "cached": false
}

Scan API

MethodPathAuthDescription
POST/public/scantfk_Scan an address (all 12 chains: Bitcoin / Ethereum / Polygon / BNB / Base / Arbitrum / Optimism / Avalanche / Solana / Kaia / TRON / XRP). Results cached for 30 minutes.
GET/public/scantfk_Query-parameter form of the same scan
POST/public/scan/asynctfk_Scan one address asynchronously (202 + job id)
POST/public/scan/batchtfk_Submit a batch scan job (Pro: 100 / Business: 200 / Enterprise: 500 addresses per job)
GET/public/scan/{job_id}tfk_Check job progress (batch and async scans share this)
GET/public/scan/{job_id}/resultstfk_Retrieve job results (includes score_semantics)
GET/public/scanstfk_List recently recorded scans (newest first, up to 100 per page)
GET/public/scans/{scan_id}tfk_Fetch one recorded scan including its detections
GET/public/usagetfk_Current-month usage and plan limits
GET/public/healthNoneAPI health check (no auth required)
POST /public/scan

Request

{
  "address": "347QFbejDBdMZFTxpmn6evvvqyXiqZTCd7",
  "chain": "bitcoin"
}

Response

{
  "success": true,
  "address": "347QFbejDBdMZFTxpmn6evvvqyXiqZTCd7",
  "chain": "bitcoin",
  "address_type": "wallet",
  "risk_level": "CRITICAL",
  "risk_score": 100,
  "detection_count": 4,
  "detections": [
    {
      "detector_id": "B2",
      "detector_name": "OFAC_SANCTIONED",
      "severity": "CRITICAL",
      "description": "Address is on OFAC SDN sanctions list",
      "details": { "category": "OFAC_SANCTIONED" }
    }
  ],
  "metadata": {
    "btc_balance": 0.0,
    "total_transactions": 69,
    "is_sanctioned": true
  },
  "ml_anomaly_score": 0.3431,
  "scan_duration_ms": 19045,
  "cached": false
}

risk_score ranges from 0 (safe) to 100 (critical). risk_level is one of CRITICAL / HIGH / MEDIUM / LOW / UNKNOWN.

Asynchronous scan (202 + polling)

Bitcoin addresses with large UTXO sets and very high-frequency EVM accounts can outlast the synchronous HTTP deadline. Send those here: you get 202 and a job id immediately, and the job is persisted so it survives a restart.

POST /public/scan/async

Response (202)

{
  "success": true,
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "address": "0x1234...",
  "chain": "ethereum",
  "status_url": "/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000",
  "results_url": "/api/v1/public/scan/550e8400-e29b-41d4-a716-446655440000/results",
  "message": "Scan queued. Poll status_url until status is 'completed'."
}

Poll status_url until status is completed, then read the results from results_url.

curl -H "X-API-Key: tfk_your_key_here" \
  https://chain-analyzer.com/api/v1/public/scan/550e8400-.../results
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "score_semantics": "risk_0to100",
  "results": [
    {
      "address": "0x1234...",
      "chain": "ethereum",
      "status": "completed",
      "risk_level": "HIGH",
      "risk_score": 78,
      "detection_count": 3,
      "detections": [],
      "metadata": {},
      "error_message": null,
      "completed_at": "2026-08-13T04:21:07Z"
    }
  ]
}

Costs the same 1 scan as the synchronous call, charged once on success. Available even on plans without batch scanning.

POST /public/scan/batch

Submit a batch scan job (Pro: 100 / Business: 200 / Enterprise: 500 addresses per job)

Request

{
  "addresses": [
    "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
    "0x1234567890123456789012345678901234567890"
  ],
  "include_ai_analysis": true,
  "notify_email": "user@example.com"
}

Response

{
  "success": true,
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "total_addresses": 2,
  "message": "Batch scan queued for 2 addresses."
}

Scan history

Reads back recorded scans for your tenant. Read-only, so it does not consume monthly quota.

curl -H "X-API-Key: tfk_your_key_here" \
  "https://chain-analyzer.com/api/v1/public/scans?limit=20&offset=0"
Coverage: single scans (web console, synchronous POST /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. Retention follows the plan history_days (Free 7 / Starter 30 / Pro 180 / Business 365 / Enterprise unlimited).

Usage and quota

Read this instead of hard-coding plan limits in client code. Read-only, so it does not consume monthly quota. A value of -1 means unlimited.

GET /public/usage
{
  "plan_id": "business",
  "scans_per_month": 3000,
  "scans_used": 412,
  "scans_remaining": 2588,
  "topup_remaining": 0,
  "period": "2026-08",
  "period_reset_at": "2026-09-01T00:00:00Z",
  "api_rate_limit_per_min": 120,
  "presign_rate_limit_per_min": 480,
  "batch_limit": 200,
  "watchlist_limit": 100,
  "webhook_limit": 5
}

Monitoring (Watchlist / Webhooks)

Keep addresses under continuous watch and receive risk changes over webhooks. Watchlist capacity and the number of webhook endpoints are enforced server-side per plan.

MethodPathAuthDescription
GET/watchlisttfk_List watchlist items
POST/watchlisttfk_Add an address to the watchlist
DELETE/watchlist/{item_id}tfk_Remove a watchlist item
POST/watchlist/{item_id}/rescantfk_Rescan now (change detection and notifications included)
PATCH/watchlist/{item_id}/followtfk_Enable Follow Mode / change depth (Pro+, depth clamped to the plan maximum)
GET/watchlist/{item_id}/discoveriestfk_List related addresses discovered by Follow Mode
GET/webhooks/configstfk_List webhook endpoints
POST/webhooks/configstfk_Register a webhook endpoint (the signing secret is returned once)
PATCH/webhooks/configs/{config_id}tfk_Partially update a webhook endpoint (URL, events, active state)
DELETE/webhooks/configs/{config_id}tfk_Delete a webhook endpoint
POST/webhooks/testtfk_Send a test delivery
GET/webhooks/deliveriestfk_Fetch the delivery log (for debugging failures)
GET/webhooks/eventsNoneList subscribable event types (no auth required)

Webhook events

EventDescription
risk.changedA watched address changes risk level
risk.high_detectedA HIGH risk is detected
risk.critical_detectedA CRITICAL risk is detected
scan.completedA scan completes
batch.completedA batch job completes
watchlist.alertGeneral watchlist alerts

Investigation (cases / report packages / exposure)

Everything the investigation UI does is available over the API: build a case, group addresses, bulk-scan it, export a unified PDF, analyze exposure, and trace fund flow. Long-running work (bulk scan, report package generation) returns 202 and is polled for status.

MethodPathAuthDescription
CRUD/casestfk_Create / fetch / update / delete cases, and manage their addresses and notes
POST/cases/{case_id}/bulk-scantfk_Start a bulk scan of the case addresses (202) and poll its progress
GET/cases/{case_id}/report.pdftfk_Unified PDF report covering every address in the case
POST / GET/cases/{case_id}/str-packagetfk_Generate and download the suspicious-transaction-report support package (evidence PDF / draft .docx / 43-column CSV) Enterprise
GET/exposure/{address}tfk_Multi-hop exposure analysis (direct and indirect, grouped by risk tier)
GET/exposure/{address}/terminalstfk_List classified endpoints reachable from one origin, with the path to each Business+
POST/graph/flow-pathtfk_Directed fund-flow path between two addresses Pro+
POST/graph/relationship-checktfk_Relationship and shared counterparties between two addresses Enterprise
POST/scan/txtfk_Scan both counterparties of a transaction by hash (all 12 chains) Enterprise
GET/risk-report/{address}tfk_Risk assessment report for a single address (PDF, with exposure and an AI summary)

POST /scan/tx resolves on all 12 chains — the 8 EVM chains plus Bitcoin, Solana, TRON, and XRP. The tx_hash format is chain-dependent: 0x + 64 hex on EVM, bare 64 hex on Bitcoin / TRON / XRP, and 87-88 character base58 on Solana. A TRC-20 transfer on TRON resolves to the token Transfer event participants rather than the contract, and an XRPL Payment reports the delivered amount rather than the requested one (non-Payment types return the accounts involved plus a note). Enterprise plans only, including the integration trial tier.

Feature details: One-shot investigationContract monitoringBatch scanning

Transfer queries (date-ranged)

Two endpoints for the reporting question an address scan cannot answer — what did this customer do with this token last quarter. Both require an explicit UTC window.

MethodPathAuthDescription
POST/tx-querytfk_Every transfer of one token touching one address in a date range, with a counterparty roll-up
POST/contract-alerttfk_Sweep a whole token contract and list the addresses matching a malicious pattern
The two modes accept deliberately different chain sets. /tx-query takes the 8 EVM chains plus Solana and TRON (TronGrid's TRC-20 account feed is time-filterable). /contract-alert takes the 8 EVM chains plus Solana — TRON is not supported there. Neither mode accepts XRP or Bitcoin: XRPL issued currencies are (currency, issuer) pairs rather than contracts, and Bitcoin has no token layer, so there is nothing for token_contract to point at. Use the address-scan surfaces (/public/scan, /scan/tx, Follow Mode) for those chains.

start_date and end_date are required. end_date must be later than start_date, and the span must not exceed 90 days — a longer window returns 400 rather than being silently truncated. At most the 500 most recent transfers are returned and analyzed, so split longer periods into several calls.

Both are read-only and cost no monthly scan quota (they do count against your plan's per-minute request budget).

POST /tx-query

Returns every transfer of the given token that touched the given address inside the window, plus a per-counterparty aggregate (counts, amounts, net) labelled from the known registry.

Request

{
  "address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "chain": "tron",
  "start_date": "2026-05-01T00:00:00Z",
  "end_date": "2026-07-30T00:00:00Z",
  "lang": "en"
}

Response

{
  "success": true,
  "chain": "tron",
  "address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
  "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "token_symbol": "USDT",
  "start_date": "2026-05-01T00:00:00Z",
  "end_date": "2026-07-30T00:00:00Z",
  "total_transfers": 128,
  "transfers": [
    {
      "tx_hash": "5f2c…",
      "block_number": 74210553,
      "timestamp": "2026-07-29T11:04:12Z",
      "from_address": "TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX",
      "to_address": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
      "amount": 24500.0,
      "token_symbol": "USDT",
      "token_decimals": 6,
      "direction": "sent",
      "counterparty": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj"
    }
  ],
  "counterparties": [
    {
      "address": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
      "label": "Unknown",
      "category": "unknown",
      "sent_count": 12,
      "received_count": 3,
      "sent_amount": 291400.0,
      "received_amount": 18000.0,
      "net_amount": -273400.0
    }
  ],
  "query_duration_ms": 4187,
  "error": null
}

A failed query is surfaced as 400 with detail, so a 200 response always carries success: true and a null error.

POST /contract-alert

The inverse of /tx-query: no address, sweep every transfer of the token and return only the addresses matching a malicious pattern. Built for issuers watching their own token continuously.

Request

{
  "token_contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "chain": "ethereum",
  "start_date": "2026-07-01T00:00:00Z",
  "end_date": "2026-07-31T00:00:00Z",
  "lang": "en"
}

Response

{
  "success": true,
  "chain": "ethereum",
  "token_contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "token_symbol": "USDT",
  "start_date": "2026-07-01T00:00:00Z",
  "end_date": "2026-07-31T00:00:00Z",
  "total_transfers_analyzed": 500,
  "flagged_addresses": [
    {
      "address": "0x1234567890123456789012345678901234567890",
      "pattern_type": "address_poisoning",
      "severity": "HIGH",
      "reason": "Zero-value transfers from an address matching the first and last 4 characters of a real counterparty",
      "tx_count": 7,
      "total_amount": 0.0,
      "sample_tx_hash": "0xabc…"
    }
  ],
  "query_duration_ms": 9042,
  "error": null
}

Detection patterns (pattern_type)

pattern_typeDescription
known_maliciousAddress already in the malicious registry
address_poisoningAddress poisoning (a lookalike address baiting a misdirected send)
relay_chainRelay chain (nearly the full amount forwarded on immediately after receipt)
rapid_distributionRapid distribution to many addresses in a short window
large_transferAnomalously large transfer

ScamDB API

Public OSINT API. No authentication required for read endpoints. IP-based rate limiting.

MethodPathAuthDescription
GET/scamdb/lookup/{address}NoneCheck if an address is in ScamDB (free, no auth)
GET/scamdb/entriesNoneList verified ScamDB entries (paginated)
GET/scamdb/entries/{scam_id}NoneGet a single ScamDB entry
GET/scamdb/statsNoneScamDB statistics
GET/scamdb/search?q=...tfk_Full-text search ScamDB (tfk_ key required)
POST/scamdb/reporttfk_Submit a scam report (tfk_ key required)
GET /scamdb/lookup/{address}
curl https://chain-analyzer.com/api/v1/scamdb/lookup/7kMpieh2THdaC5eUvxFJDL3TdsQWVQCwdhsEjLj1eL26

Response

{
  "found": true,
  "entry": {
    "id": "SCAM-001",
    "address": "7kMpieh2THdaC5eUvxFJDL3TdsQWVQCwdhsEjLj1eL26",
    "type": "drainer",
    "severity": "danger",
    "domains": ["solland.cc", "hibit.app"],
    "method": "FCFS airdrop phishing",
    "total_stolen_usd": 3700,
    "verified": true
  },
  "match_type": "exact"
}

Rate Limits

PlanRequests/minDescription
Pro60REST API
Business120REST API
Enterprise300REST API
Pro / Business / Enterprise240 / 480 / 1200Screening (separate bucket)
Any (IP-based)30ScamDB reads
Successful responses on the main endpoints carry X-RateLimit-Limit and X-RateLimit-Remaining. On a 429, retry after the number of seconds in Retry-After.

ScamDB reads: IP-based 30 req/min. A 429 error is returned when exceeded.

Monthly scan quota

PlanScans/month
Free10
Starter200
Pro1,000
Business3,000
EnterpriseUnlimited

Only execution charges quota. A synchronous or asynchronous scan costs 1; batch and case bulk scans cost 1 per address, charged only for addresses that complete. Screening, usage, history, and report downloads cost nothing.

Error Codes

CodeDescription
400Bad request (invalid parameters)
401Authentication error (API key is invalid or missing)
403Access denied (plan feature restriction)
404Resource not found
409Conflict (e.g. a job is already running for this case)
410The artifact cannot be rebuilt — regenerate it
422Validation error (missing required field, wrong type)
429Rate limit exceeded, or monthly quota exhausted
500Server error

Error handling

Errors use the standard FastAPI {"detail": "..."} envelope. What your client should do differs by status.

401 — Authentication

A missing X-API-Key (or Authorization: Bearer) returns detail: "API key required"; a revoked or unknown key returns detail: "Invalid API key". Retrying will not help — suspect the wrong key or a revocation. Key and plan changes propagate within 5 minutes.

{ "detail": "Invalid API key" }

403 — Plan restriction

The key is valid but the feature is not on its plan. The messages are fixed per feature (for example "Transaction-hash scan is an Enterprise feature.", "Batch scanning not available on your plan", "Watchlist limit reached (100 items)"). Retrying is pointless; this needs an upgrade or fewer registered items.

{ "detail": "Transaction-hash scan is an Enterprise feature." }

404 — Not found

The resource does not exist, or belongs to another tenant — the two are deliberately indistinguishable so existence does not leak. Suspect a wrong job or case id, or an artifact past its retention window.

422 — Validation

A body field or path parameter has the wrong shape (a missing required field, a scan_id that is not a UUID). This is FastAPI's structured validation error, so detail is an array naming the location and reason. Unlike a 400, the request never reached the handler.

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "token_contract"],
      "msg": "Field required"
    }
  ]
}

429 — Rate limits and quota

Two different conditions share this status, and the headers tell them apart mechanically.

(a) Per-minute rate limit — carries Retry-After

detail reads "Rate limit exceeded. Max 120 requests per minute." and the response carries Retry-After (seconds), X-RateLimit-Limit, and X-RateLimit-Remaining: 0. Waiting the stated interval always clears it, so back off and retry.

HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0

{ "detail": "Rate limit exceeded. Max 120 requests per minute." }

(b) Monthly scan quota exhausted — no Retry-After

detail is a fixed Japanese message including the counts, returned in Japanese regardless of Accept-Language or the lang parameter. There is no Retry-After, and no amount of backoff clears it before the next UTC month — this needs a plan upgrade or a scan pack. Endpoints that take an explicit list of addresses report the shortfall separately ("N scans remaining but M requested") rather than silently truncating your list.

HTTP/1.1 429 Too Many Requests

{
  "detail": "月間スキャン上限に達しています(1000 / 1000 件)。プランのアップグレード、またはスキャンパックの購入をご検討ください。"
}
How to tell them apart: a 429 with Retry-After is a rate limit and clears on its own; a 429 without one is a quota exhaustion and does not. Reading scans_remaining from GET /public/usage catches it before it happens.

Score direction

The other thing integrators get backwards as often as error handling is the score direction. Both risk_score and score run 0 (safe) to 100 (critical) — higher is riskier. History, batch, and webhook payloads carry score_semantics: "risk_0to100" so your client can assert the convention rather than assume it.

SDK (Coming Soon)

SDKs for Python, JavaScript, and Go are in development.

Python JavaScript Go

Share this page

© 2026 ChainAnalyzer. All rights reserved.