# Arkham Intel API v1.1.0 > Production-grade blockchain intelligence API. Entity attribution, address labels, transfer tracking, and on-chain analytics across 20+ chains. - Base URL: `https://api.arkm.com` - API Docs: https://intel.arkm.com/api/docs - OpenAPI Spec: https://intel.arkm.com/openapi.json - Schemas: https://intel.arkm.com/llms/schemas/ - Endpoint Docs: https://intel.arkm.com/llms/-.md — slug is the HTTP method + path with `/` replaced by `-` and `{braces}` removed (e.g. `POST /intelligence/address/batch/all` → https://intel.arkm.com/llms/post-intelligence-address-batch-all.md) This file contains the full API guide, an endpoint reference table, and code examples. For detailed per-endpoint documentation (parameters, schemas, cURL examples), fetch the individual markdown files linked in the Endpoint Reference table below. --- # API Guide ## Overview **User guide for integrating the Arkham Intel API** Production-grade blockchain intelligence for your products, pipelines, and workflows. **Ready to start?** [Get API Access →](/getting-started/access) or jump to the [API Reference](#api-reference) ## Introduction This guide complements the [API Reference](#api-reference) with practical guidance on using the API effectively. It covers common integration topics including access and onboarding, authentication, rate limits, pagination, and the Arkham data model (addresses, entities, labels, and tags). Designed for both private- and public-sector users, this guide focuses on implementation guidance, conceptual clarity, and frequently asked questions. For endpoint specifications, request/response schemas, and code examples, see the [API Reference](#api-reference). This guide is continuously updated as the API expands and new best practices emerge. ## What This Guide Covers - **Access and onboarding** - How to get API access - **API keys and authentication** - Managing your credentials - **Rate limits** - Understanding and working within limits - **Credit pricing** - How API credits are calculated and billed per endpoint - **Pagination patterns** - Getting all your data - **Data model** - Addresses, entities, labels, and tags - **Best practices** - Security and operational recommendations ## Core Principles Entity-First Built around real-world actors, not isolated addresses Confidence-Scored Attribution is probabilistic, not binary claims Living Intelligence Labels evolve as new signals emerge ## When to Use This Guide vs API Docs Use This Guide When... - Setting up an integration - Clarifying expected behavior - Understanding the data model - Learning best practices Use API Docs When... - You need endpoint specifications - You need request/response schemas - You need code examples - You need parameter details ## Quick Links - **API Reference:** [API Reference](#api-reference) - **Request API Access:** [intel.arkm.com/api](https://intel.arkm.com/api) - **Arkham Platform:** [intel.arkm.com](https://intel.arkm.com) - **LLM-Friendly Docs:** [intel.arkm.com/llms.txt](https://intel.arkm.com/llms.txt) — machine-readable API docs for AI assistants - **Support:** [api@arkm.com](mailto:api@arkm.com) --- _Last updated: January 2026_ ## Design Principles A few core principles shape how the Arkham API is designed and how Arkham intelligence should be used. ## Entity-First, Not Address-First Arkham is built around **entities** (real-world actors), not isolated wallet addresses. Addresses are often disposable; entities persist. The API is optimized to help you understand activity at the **actor level** and then drill down into underlying addresses when needed. ## Explicit Confidence, Not Implied Certainty Attribution is treated as a **confidence-scored** problem, not a binary claim. Arkham distinguishes between higher-confidence verified labels and lower-confidence predictive attribution, so consumers can make informed decisions and apply appropriate corroboration. ## Labels as Living Intelligence Labels and entity composition evolve as new signals emerge and attribution improves. Arkham intelligence is continuously updated, and the API is designed to support workflows that account for **ongoing enrichment and corrections** over time. ## Coverage Without Hiding Uncertainty Arkham aims for broad coverage while being transparent about what is known versus inferred. Where attribution is probabilistic, the system surfaces that uncertainty rather than hiding it, enabling users to balance **coverage, confidence, and risk** according to their use case. --- See the [Data Model](/concepts/data-model) section for more details on how Arkham attribution coverage should be interpreted, including how to interpret labels vs. prediction labels, how Arkham represents confidence in attribution, and how to incorporate those signals into production workflows. ## Who This API Is For The Arkham API is built for teams that need **production-grade blockchain intelligence** inside their own products, pipelines, and investigative workflows. It provides direct programmatic access to the same intelligence layer that powers the Arkham platform, including entity attribution, labels, tags, transfers, wallet clusters, and entity predictions. At the core of the API is **ULTRA**, Arkham's proprietary address-matching engine. ULTRA links blockchain addresses to real-world entities so sophisticated users can customize data flows, automate monitoring, and integrate Arkham intelligence into internal systems. ## What You Can Query Through the API, you can access: - **Labels and tags** for addresses and entities - **Transaction activity** (transfers, counterparties, historical flows) - **Historical balance data** for addresses and entities ::: tip Scale & Coverage Arkham powers the world's largest blockchain intelligence database: - **3B+ tags** - **94% of on-chain volume attributed** - **$1T+ in labeled asset flows tracked** ::: ## Private Sector Use Cases The API is designed for organizations that need intelligence for trading, operations, and risk: - **Trading firms** (market makers, hedge funds, asset managers) - **Exchanges**, **DeFi protocols**, **payment providers**, and **crypto infrastructure** - **Risk management** across portfolios, venues, and counterparties - **Investment strategy** and research (flows, entities, market behavior) - **Real-time market intelligence** and monitoring (alerts, key wallet tracking) - **Compliance and fraud** workflows (AML checks, suspicious activity detection, exposure mapping) ## Public Sector Use Cases The API is designed for mission-driven environments where attribution and traceability matter: - **Government investigators and analysts**, often in SI- or CSP-hosted environments - **Systems integrators** embedding attribution into investigative workflows - **SaaS and data platforms** integrating crypto intelligence into their products - **Financial crime, sanctions, cybercrime, and fraud** analytics teams ## Getting Access To use the Arkham API, you'll need: 1. **An Arkham account** 2. **An API plan** 3. **An API key** generated from your Arkham account ## Step 1: Create an Arkham Account 1. Go to [intel.arkm.com](https://intel.arkm.com) 2. Click **Sign Up** in the top-right corner 3. Complete registration and log in ## Step 2: Get an API Plan Arkham API plans are **custom-built** based on your organization's needs, use cases, and expected usage. **To request access:** 1. Go to [intel.arkm.com/api](https://intel.arkm.com/api) 2. Scroll to the bottom and complete the short form 3. Submit your details Our team reviews inbound requests daily and will follow up with either: - A **tailored proposal** (if you already know your use case and expected usage), or - **Trial access** so you can evaluate Arkham's data and endpoints before committing ## Step 3: Generate an API Key Once you have an API plan or trial access, you can generate and manage keys directly in the Arkham platform: 1. Go to [intel.arkm.com/settings](https://intel.arkm.com/settings) (while signed in) 2. Navigate to the **API / Developer** section 3. Create, view, or manage your keys ::: info Key Limits By default, accounts with API access can create **up to 5 API keys**. On custom plans, this limit can be increased as needed. ::: You can manage your keys from the settings page at any time, including **naming**, **deleting**, and **rotating** keys. ## API Keys & Authentication In the Arkham User Interface: 1. Go to **Settings** → scroll to **API Keys** 2. Click **Create key** 3. **Name** the key (e.g., `prod-backend`, `staging`, `research-laptop`) 4. Copy it **once** and store it securely (password manager / secret manager) ::: warning Store Securely Your API key is only shown once. Store it immediately in a secure location. ::: ## Using the API Key All API requests **must** include a valid `API-Key` header. Requests without it will be rejected as unauthorized. **Header format:** ``` API-Key: ``` **Example (curl):** ```bash curl -H "API-Key: " "https://api.arkm.com/..." ``` ## Multiple Keys Yes, you can have multiple keys. **Best practice:** - Separate keys per **environment** (prod / staging / dev) - Separate keys per **service** (backend / analytics job / partner integration) This makes rotation and incident containment much easier. ## Rotating or Revoking Keys 1. Create a **new** key 2. Deploy it and confirm requests succeed 3. Revoke the **old** key in the dashboard ## REST vs WebSocket The Arkham API offers two kinds of endpoints: REST (HTTPS) Lookups, history, analytics, enrichment WebSocket (streaming) Real-time updates, live feeds, events ### When to Use REST - Historical data - Deterministic queries - Batch jobs and enrichment - Easy retry logic ### When to Use WebSocket - Low-latency updates - Continuous streaming without polling - Event-driven systems ::: tip WebSockets are for "what's changing right now"; REST is for "give me the full state / history." ::: ## Addresses, Entities, Labels Address A single on-chain address (wallet or contract) `0x1234...abcd` Entity A set of addresses attributed to the same real-world actor Binance, BlackRock, Uniswap **Entities are the "who". Addresses are the "where".** ## Labels **Labels** are identity metadata Arkham attaches to an address or entity (e.g., "Binance", "BlackRock", "Chainlink"). Labels are designed to answer the most important question in blockchain analysis: > **Who is behind this activity?** Labels let users interpret transactions quickly without manually investigating every trail on-chain. ## Tags **Tags** are qualitative descriptors that summarize the **type** or **behavior** of an address/entity, whether it is labeled or not. **Examples:** - "Fund" - "BTC Whale" - "OFAC Sanctioned" - "Hacker" - "Banned by USDT" - "High Transacting" Tags are useful for filtering, discovery, and grouping similar actors. ## Label Types & Confidence Arkham provides two label types: ### 1. Arkham Verified Labels Verified labels represent Arkham's highest-confidence attribution. - **How to identify:** Displayed with a **blue badge** on the Arkham platform - **Confidence:** Very high (internally thresholded, typically **≥98%**) ::: tip Use verified labels when you need maximum attribution reliability. ::: ### 2. Entity Predictions Entity Prediction labels are programmatically generated to identify addresses likely connected to a known entity. - **How to identify:** Displayed with a **pink badge**, often with a question mark - **Confidence:** Lower than verified (internally thresholded, typically **≥80%**) ::: warning Prediction labels are valuable for expanding coverage and spotting relationships, but should be treated as **probabilistic attribution** rather than certainty. ::: ## Entity Composition Changes Entity composition can change as new attribution is discovered and additional addresses are linked. Arkham's attribution database is continuously updated: - New entities are added - Existing entities expand as new addresses are identified - In rare cases, attribution may be corrected For enterprise users, Arkham offers API access to monitor database updates in near real time. ## Private Labels Arkham doesn't have a label you know about? You can add **private labels** via both the UI and the API. Private labels are visible only to: - Your account/workspace - Anyone you explicitly share access with With private labels you can: - Create a new custom entity - Add addresses to an existing entity - Manage your private data via the [Labels page](https://intel.arkm.com/labels) ::: info Privacy Arkham does not access or publish users' private labels. ::: ## How Arkham Labels Wallets Arkham labels wallets by combining **on-chain behavior** with **public off-chain signals**, running that through our proprietary attribution system (**ULTRA**), and only publishing labels when they meet strict confidence thresholds. ## The Process (High Level) ### 1. Data Ingestion (Signals) On-chain - Transfers and interactions - Counterparty networks - Contract behavior - Timing patterns Off-chain (public) - Public disclosures - Official wallet posts - Protocol docs - Exchange/protocol announcements - Public attribution sources ### 2. Attribution & Clustering (ULTRA) ULTRA applies a mix of: - **Address clustering** - linking related addresses - **Address modeling** - behavioral patterns - **ML-driven heuristics** - probabilistic attribution - **Intelligence augmentation** - enrichment from external public signals - **Internal consistency checks** - across multiple data sources ### 3. Confidence Gating Verified labels ≥98% Prediction labels ≥80% Prediction labels are explicitly marked as lower-confidence. ### 4. Human Verification Labels that are high visibility or high impact can be reviewed **case-by-case** by analysts before publication. ## Why This Matters Labeling isn't a single "source of truth" problem. It's a **signal fusion** problem: ULTRA combines multiple independent signals and only publishes attribution when the evidence crosses defined confidence thresholds. This approach provides: - **Accuracy** - High confidence thresholds reduce false positives - **Coverage** - ML-driven predictions expand coverage beyond manual labeling - **Transparency** - Confidence scoring lets you decide how to weight attribution - **Adaptability** - Continuous updates as new signals emerge ## Rate Limits Yes. Rate limits vary by plan and may also depend on the endpoint type (REST vs WebSocket). Arkham API endpoints generally fall into two categories: Standard Most endpoints, with small burst allowance **20 requests/second** Heavy Resource-intensive endpoints **1 request/second** Both limits can be **scaled up** under custom API plans. ## Increasing Your Limits If you need higher limits for a specific endpoint or across your entire plan, contact [api@arkm.com](mailto:api@arkm.com). ## What Happens When You Hit Limits If you exceed your limit, requests are rejected with: - **HTTP 429** (Too Many Requests) - A "too many requests" error response ## Avoiding Rate Limits Best practices for staying within limits: 1. **Cache** responses when possible 2. **Paginate correctly** (don't repeatedly hit the first page) 3. Use **bulk endpoints** where available 4. Implement **retries with exponential backoff + jitter** on 429 ### Backoff Example (Python) ```python import time import random import requests def request_with_backoff(url, headers, max_retries=5): for attempt in range(max_retries): resp = requests.get(url, headers=headers) if resp.status_code == 429: # Get retry delay from header, or use exponential backoff retry_after = int(resp.headers.get('Retry-After', 2 ** attempt)) # Add jitter to prevent thundering herd jitter = random.uniform(0, 1) time.sleep(retry_after + jitter) continue return resp raise Exception("Max retries exceeded") ``` ::: tip Always add jitter to your backoff delays to prevent synchronized retry storms. ::: ## Credit Pricing Every API request consumes **credits** from your monthly allowance. The cost depends on the endpoint and, for some endpoints, the amount of data returned. Per-Call Fixed credit cost each time you call the endpoint **Credits = weight** Per-Row Credits scale with the number of results returned **Credits = weight × rows returned** ::: info Failed requests (4xx/5xx responses) are not charged. ::: ## Endpoint Credit Pricing ### Intelligence | Endpoint | Credits | Type | |----------|---------|------| | `/intelligence/address/{address}` | 1 | per call | | `/intelligence/address/{address}/all` | 2 | per call | | `/intelligence/address_enriched/{address}` | 2 | per call | | `/intelligence/address_enriched/{address}/all` | 4 | per call | | `/intelligence/entity/{entity}` | 1 | per call | | `/intelligence/entity/{entity}/summary` | 1 | per call | | `/intelligence/entity_predictions/{entity}` | 1 | per call | | `/intelligence/contract/{chain}/{address}` | 1 | per call | | `/intelligence/token/{id}` | 1 | per call | | `/intelligence/token/{chain}/{address}` | 1 | per call | | `/intelligence/addresses/updates` | 100 | per call | | `/intelligence/entities/updates` | 30 | per call | | `/intelligence/tags/updates` | 30 | per call | | `/intelligence/address_tags/updates` | 100 | per call | | `POST /intelligence/address/batch` | 250 | per call | | `POST /intelligence/address_enriched/batch` | 500 | per call | | `POST /intelligence/address/batch/all` | 500 | per call | | `POST /intelligence/address_enriched/batch/all` | 1000 | per call | | `/intelligence/search` | 30 | per call | | `/intelligence/entity_balance_changes` | 2 | per call | | `/intelligence/entity_types` | 1 | per call | ### Transfers & Swaps | Endpoint | Credits | Type | |----------|---------|------| | `/transfers` | 2 | per row | | `/swaps` | 2 | per row | | `/transfers/histogram/simple` | 2 | per call | | `/transfers/histogram` | 4 | per call | ::: warning Per-Row Billing `/transfers` and `/swaps` charge per result returned. A request returning 50 transfers costs 50 × 2 = **100 credits**. Use the `limit` parameter to control costs. ::: ### WebSocket | Endpoint | Credits | Type | |----------|---------|------| | `POST /ws/sessions` | 500 | per call | | `GET /ws/sessions` | 1 | per call | | `GET /ws/sessions/{id}` | 1 | per call | | `DELETE /ws/sessions/{id}` | 1 | per call | | `/ws/transfers` | 2 | per row | | `/ws/session-info` | 1 | per call | | `/ws/active_connections` | 1 | per call | ### Token | Endpoint | Credits | Type | |----------|---------|------| | `/token/top` | 10 | per call | | `/token/trending` | 1 | per call | | `/token/trending/{id}` | 1 | per call | | `/token/holders/{id}` | 30 | per call | | `/token/holders/{chain}/{address}` | 30 | per call | | `/token/top_flow/{id}` | 10 | per call | | `/token/top_flow/{chain}/{address}` | 10 | per call | | `/token/volume/{id}` | 3 | per call | | `/token/volume/{chain}/{address}` | 3 | per call | | `/token/balance/{id}` | 1 | per call | | `/token/balance/{chain}/{address}` | 1 | per call | | `/token/arkham_exchange_tokens` | 1 | per call | | `/token/price/history/{id}` | 1 | per call | | `/token/price/history/{chain}/{address}` | 1 | per call | | `/token/market/{id}` | 1 | per call | | `/token/price_change/{id}` | 1 | per call | | `/token/addresses/{id}` | 1 | per call | ### Transaction | Endpoint | Credits | Type | |----------|---------|------| | `/tx/{hash}` | 2 | per call | | `/transfers/tx/{hash}` | 2 | per call | ### History | Endpoint | Credits | Type | |----------|---------|------| | `/history/entity/{entity}` | 2 | per call | | `/history/address/{address}` | 1 | per call | ### Portfolio | Endpoint | Credits | Type | |----------|---------|------| | `/portfolio/entity/{entity}` | 2 | per call | | `/portfolio/address/{address}` | 1 | per call | | `/portfolio/timeSeries/entity/{entity}` | 3 | per call | | `/portfolio/timeSeries/address/{address}` | 2 | per call | ### Balances & Loans | Endpoint | Credits | Type | |----------|---------|------| | `/balances/address/{address}` | 1 | per call | | `/balances/entity/{entity}` | 5 | per call | | `/balances/solana/subaccounts/address/{addresses}` | 1 | per call | | `/balances/solana/subaccounts/entity/{entities}` | 3 | per call | | `/loans/address/{address}` | 1 | per call | | `/loans/entity/{entity}` | 3 | per call | ### Counterparties & Flow | Endpoint | Credits | Type | |----------|---------|------| | `/counterparties/address/{address}` | 50 | per call | | `/counterparties/entity/{entity}` | 50 | per call | | `/flow/address/{address}` | 2 | per call | | `/flow/entity/{entity}` | 3 | per call | | `/volume/address/{address}` | 1 | per call | | `/volume/entity/{entity}` | 2 | per call | ### Tags, Clusters & Labels | Endpoint | Credits | Type | |----------|---------|------| | `/tag/{id}/params` | 5 | per call | | `/tag/{id}/summary` | 1 | per call | | `/cluster/{id}/summary` | 1 | per call | | `/user/labels` | 1 | per call | | `POST /user/labels` | 1 | per call | | `/user/entities` | 1 | per call | | `/user/entities/{id}` | 1 | per call | | `PUT /user/entities/only_add/{id}` | 1 | per call | ### Market Data & Networks | Endpoint | Credits | Type | |----------|---------|------| | `/marketdata/altcoin_index` | 1 | per call | | `/networks/history/{chain}` | 1 | per call | | `/chains` | 0 | free | | `/networks/status` | 0 | free | | `/arkm/circulating` | 0 | free | ::: tip Endpoints with credit cost **0** are free and do not consume credits. ::: ## Monitoring Your Usage You can track your credit consumption in real time on the [API Dashboard](https://intel.arkm.com/api-dashboard). The dashboard shows per-endpoint breakdowns, hourly usage trends, and remaining credits for your billing period. ## Need Help? For questions about credit usage, custom pricing, or plan upgrades, contact [api@arkm.com](mailto:api@arkm.com). ## Pagination The Arkham API uses **offset-based pagination**: - **`limit`** — Maximum results per page (defaults vary by endpoint) - **`offset`** — Number of results to skip (default: 0) Responses include `count` or `total` showing total matching records. ## Pagination Limits by Endpoint `/transfers` Default: 20 · Max: No hard cap · *50-500 recommended* `/swaps` Default: 50 · Max: No hard cap · *50-500 recommended* `/counterparties/*` Default: 1000 · Max: **1000** · *Hard limit enforced* `/tokens` Default: 100 · Max: 250 `/tag/{id}/params` Default: 100 · Max: 1000 ::: warning Why 50-500 recommended? Large result sets increase response time and credit cost. Elasticsearch queries have internal timeouts (~20s for complex queries), and performance might degrade above ~1000 results. ::: ## Iterating Through Results ### Option 1: Offset-Based (Simple) Simple but may miss/duplicate items if data changes during pagination: ```python offset, all_results = 0, [] while True: resp = api.get_transfers(limit=50, offset=offset) all_results.extend(resp['transfers']) if len(resp['transfers']) < 50: break offset += 50 ``` ### Option 2: Time-Window Sliding (Consistent) For frequently-changing data, page by time windows instead of offsets: ```python from datetime import datetime, timedelta window_end = datetime.utcnow() window_size = timedelta(hours=1) all_results = [] while window_end > earliest_date: window_start = window_end - window_size resp = api.get_transfers( timeGte=int(window_start.timestamp() * 1000), timeLte=int(window_end.timestamp() * 1000), limit=500 ) all_results.extend(resp['transfers']) window_end = window_start # slide window back ``` ::: tip Time-window sliding eliminates duplicates and missed records caused by new data arriving during pagination. ::: ## Timestamps & Sorting ### Absolute (Unix milliseconds) ``` ?timeGte=1696630274000&timeLte=1696716674000 ``` ### Relative Duration ``` ?timeLast=24h ``` Also supports: `7d`, `30d`, `1M`, `1y` ::: danger Cannot combine `timeLast` with `timeGte`/`timeLte` — returns HTTP 400. ::: ## Sorting - **`sortKey`** — `time` (default), `value`, `usd` - **`sortDir`** — `desc` (default), `asc` **Example:** ``` GET /transfers?sortKey=usd&sortDir=desc&timeLast=24h ``` ## Time Handling Notes - All timestamps are **UTC** - `timeLast` is calculated from server time at request - `/portfolio` time parameter is truncated to **UTC midnight** ## Errors & HTTP Codes - **200** — Success - **400** — Bad request (invalid params) - **401** — Unauthorized (API key issue) - **402** — Payment required - **403** — Forbidden (endpoint not in plan) - **404** — Not found - **429** — Rate limit exceeded - **500** — Server error Error responses follow this format: ```json {"error": "description"} ``` ## Common Errors `"error validating API key"` (401) Check key, use `API-Key` header (not `Authorization`) `"not allowed to access endpoint"` (403) Contact api@arkm.com for plan upgrade `"cannot use timeLast and timeGte together"` Use one or the other, not both `"limit must be between 1 and 1000"` Reduce limit (counterparties max: 1000) ## Rate Limit Handling (429) When you receive a 429, implement backoff: ```python import time import requests def request_with_backoff(url, headers, max_retries=5): for attempt in range(max_retries): resp = requests.get(url, headers=headers) if resp.status_code == 429: time.sleep(int(resp.headers.get('Retry-After', 60))) continue return resp ``` ## WebSocket Errors WebSocket errors are returned in this format: ```json { "type": "error", "payload": { "code": "INVALID_FILTER", "message": "..." } } ``` ### Error Codes - **`INVALID_PAYLOAD`** — Malformed request - **`INVALID_FILTER`** — Invalid filter parameters - **`INSUFFICIENT_CREDITS`** — Out of credits - **`TIER_RATE_LIMITED`** — Rate limit for your tier ### Credit Exhaustion When credits are exhausted: ```json { "type": "error", "payload": { "code": "INSUFFICIENT_CREDITS", "limitType": "hourly", "resetIn": 1800 } } ``` - **`code`** — `INSUFFICIENT_CREDITS` or `TIER_RATE_LIMITED` - **`limitType`** — Which limit: `minutely`, `hourly`, or `monthly` - **`resetIn`** — Seconds until the limit resets ## Security & Best Practices ### DO - Store in environment variables or secrets manager - Use separate keys per environment (prod / staging / dev) - Use separate keys per service (backend / analytics / partner) - Rotate periodically - Always use header: `API-Key: YOUR_KEY` ### DON'T - Commit to version control - Put in URLs as query parameters - Log in plaintext - Share across organizations - Expose in client-side code ## Query Optimization ### 1. Add Filters to Reduce Result Size ``` ?chain=ethereum&usdGte=10000&timeLast=24h ``` ### 2. Use Appropriate Time Windows - **Real-time monitoring** → `1h` or less - **Daily analysis** → `24h` - **Historical research** → `30d` ### 3. Implement Caching - **Intelligence data** → Hours - **Historical transfers** → Indefinitely - **Balances** → Minutes ## WebSocket Best Practices 1. **Always include filters** — use `from`, `to`, `tokens`, or `usdGte >= 10M` 2. **Save `sessionId`** for reconnection (valid 5 minutes) 3. **Implement ping/pong heartbeat** to detect disconnections ### WebSocket Quotas WebSocket connections consume credits based on: - **Connection time**: Charged per minute of active connection - **Transfer notifications**: Charged per notification (weight varies by plan) **Quota tiers:** - Per-minute (burst protection) - Per-hour - Per-month ### Recovery After Disconnection - Events are **buffered for 5 minutes** during disconnection - Reconnect using `sessionId` to restore filters and receive pending notifications - After 5 minutes, session expires and buffered events are discarded ## Production Checklist Before going to production, verify: - [ ] API keys stored in secrets manager (not in code) - [ ] Client-side rate limiting implemented - [ ] Exponential backoff for 429/5xx errors - [ ] Error handling for all HTTP status codes - [ ] Request logging enabled (without secrets) - [ ] Monitoring for 429/5xx rates - [ ] Caching strategy per endpoint type - [ ] Separate keys per environment ## Evidentiary Usage Arkham provides **investigative intelligence** designed to accelerate attribution, triage, and on-chain analysis. It is intended to support investigations and decision-making, not to serve as standalone evidence. ::: warning Customers are responsible for corroborating findings and meeting their own legal, regulatory, and evidentiary standards. ::: Arkham emphasizes: - **Confidence-scored attribution** - **Auditability and transparency** - Clear explanations of why labels exist and how they were derived ## Cookbook & Starter Kits This page provides ready-to-use tools and code examples to help you start using the Arkham API quickly. Whether you prefer a visual interface, copy-paste code snippets, or full command-line tools, we have options for different workflows. **What's here:** - **Postman Collection** — Import into Postman and start making requests immediately, no coding required - **Code Snippets** — Minimal Python and curl examples you can copy directly into your projects All tools come pre-configured with example addresses and sensible defaults so you can test the API right away. ## Postman Collection [Postman](https://www.postman.com/) is a free API client that lets you explore and test APIs without writing code. It's the fastest way to start making requests — just import our collection, add your API key, and click Send. **What you get:** 50+ pre-configured endpoints organized by use case (Compliance, Trading, Investigation, etc.) ::: warning WebSocket Limitation Postman cannot stream WebSocket data. For real-time transfer streaming, use the [Python code snippet](#websocket-stream-real-time-transfers) below. ::: ### Download Both Files 1. [**Download Collection JSON ↓**](/cookbook/postman/Arkham-API-Complete.postman_collection.json) — 50+ API endpoints 2. [**Download Environment JSON ↓**](/cookbook/postman/Arkham-API-Production.postman_environment.json) — Pre-configured variables ### Setup Instructions 1. Download both files above 2. Open Postman and click **Import** → Select both files 3. Click **Environments** in the left sidebar 4. Click **Arkham API - Production** to open it 5. Find the `apiKey` variable and paste your API key in the **Value** column 6. Click **Save**, then select this environment from the dropdown (top right) 7. Start exploring endpoints in the **Collections** sidebar ### What You Should See **Collection** — 50+ endpoints organized by use case: ![Postman Collection](/cookbook/postman-endpoint-example.png) **Environment** — Pre-configured variables (set your `apiKey` here): ![Postman Environment](/cookbook/postman-environment.png) ## Quick Code Examples ### REST: Get Address Intelligence **Python:** ```python import requests API_KEY = "your-api-key" address = "0x28C6c06298d514Db089934071355E5743bf21d60" # Binance response = requests.get( f"https://api.arkm.com/intelligence/address/{address}", headers={"API-Key": API_KEY} ) print(response.json()) ``` **curl:** ```bash curl -H "API-Key: YOUR_KEY" \ "https://api.arkm.com/intelligence/address/0x28C6c06298d514Db089934071355E5743bf21d60" ``` ### REST: Get Recent Transfers **Python:** ```python import requests API_KEY = "your-api-key" response = requests.get( "https://api.arkm.com/transfers", headers={"API-Key": API_KEY}, params={ "base": "0x28C6c06298d514Db089934071355E5743bf21d60", # Binance "timeLast": "1h", "limit": 10 } ) print(response.json()) ``` **curl:** ```bash curl -H "API-Key: YOUR_KEY" \ "https://api.arkm.com/transfers?base=0x28C6c06298d514Db089934071355E5743bf21d60&timeLast=1h&limit=10" ``` ### WebSocket: Stream Real-Time Transfers The Arkham API includes WebSocket endpoints for subscribing to real-time event feeds. This example connects to the transfers WebSocket and streams CEX withdrawals above $10k as they happen on-chain. ::: tip Filter Requirement WebSocket subscriptions require at least one of: `from`, `to`, `tokens`, or `usdGte >= 10,000,000`. ::: ::: info Latency Note The first transfer after subscribing may take up to 90 seconds, depending on how quickly a matching transfer occurs. ::: ```python import asyncio import json import ssl import websockets API_KEY = "your-api-key" # WARNING: This disables SSL verification for easier local testing. # For production use, remove these lines and use default SSL verification. ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE async def stream_transfers(): url = "wss://api.arkm.com/ws/transfers" headers = {"API-Key": API_KEY} async with websockets.connect(url, additional_headers=headers, ssl=ssl_context) as ws: # Subscribe to transfers from CEXs >= $10k subscribe_msg = { "id": "1", "type": "subscribe", "payload": { "filters": { "from": ["type:cex"], "usdGte": 10000 } } } await ws.send(json.dumps(subscribe_msg)) # Wait for ACK ack = await ws.recv() print("Subscribed:", json.loads(ack)) # Stream transfers async for message in ws: data = json.loads(message) if data.get("type") == "transfer": transfer = data["payload"]["transfer"] print(f"${transfer['historicalUSD']:,.0f} {transfer['tokenSymbol']} on {transfer['chain']}") asyncio.run(stream_transfers()) ``` ## WebSocket Filter Reference When subscribing to the WebSocket, you can filter transfers by: - `usdGte` (number) — Minimum USD value - `usdLte` (number) — Maximum USD value - `chains` (string[]) — List of blockchains - `from` (string[]) — Source addresses or entity names - `to` (string[]) — Destination addresses or entity names - `tokens` (string[]) — Token symbols or addresses **Filter Requirements:** You must include at least one of: - `from`, `to`, or `tokens` filter, **OR** - `usdGte >= 10,000,000` (10 million USD) **Rate Limits:** - 10,000 transfers per hour - 1,000,000 transfers per month ## Troubleshooting ### Common Errors | Error | Solution | |-------|----------| | Error validating API key | Check your key is correct. Use `API-Key` header, not `Authorization`. | | Filter requirement not met | WebSocket needs `from`, `to`, `tokens`, or `usdGte` ≥ 10M | | HTTP 401 Unauthorized | Invalid or expired API key | | HTTP 429 Too Many Requests | Rate limited — implement backoff, reduce request frequency | ## More Resources - **API Reference:** [API Reference](#api-reference) - **Request API Access:** [intel.arkm.com/api](https://intel.arkm.com/api) - **Support:** [api@arkm.com](mailto:api@arkm.com) ## Support & Contact - **API Support** (plans, custom requests, technical issues) → [api@arkm.com](mailto:api@arkm.com) - **General Support** (non-API) → [support@arkm.com](mailto:support@arkm.com) ## Product Links - **Arkham Intel (Web App)** → [intel.arkm.com](https://intel.arkm.com) - **API Documentation** → [API Reference](#api-reference) - **API Access Request** → [intel.arkm.com/api](https://intel.arkm.com/api) - **Arkham Exchange** → [arkm.com](https://arkm.com) ## LLM-Friendly Docs Machine-readable versions of these docs for use with AI assistants and LLM-powered tools: - **llms.txt** (index) → [intel.arkm.com/llms.txt](https://intel.arkm.com/llms.txt) - **llms-full.txt** (complete guide + endpoint reference) → [intel.arkm.com/llms-full.txt](https://intel.arkm.com/llms-full.txt) - **Per-endpoint docs** → `intel.arkm.com/llms/-.md` (e.g. [get-transfers.md](https://intel.arkm.com/llms/get-transfers.md)) ## Research - **Arkham Research** → [info.arkm.com/research](https://info.arkm.com/research) ## Social - **X (Twitter)** → [x.com/arkham](https://x.com/arkham) - **LinkedIn** → [linkedin.com/company/arkhamintelligence](https://linkedin.com/company/arkhamintelligence) - **YouTube** → [youtube.com/@ArkhamIntel](https://youtube.com/@ArkhamIntel) - **Telegram** → [t.me/arkhamintelligence](https://t.me/arkhamintelligence) - **Discord** → [discord.gg/arkham](https://discord.gg/arkham) --- ## Endpoint Reference | Method | Path | Summary | Key Parameters | |--------|------|---------|----------------| | [GET](https://intel.arkm.com/llms/get-arkm-circulating.md) | `/arkm/circulating` | Get ARKM circulating supply | | | [GET](https://intel.arkm.com/llms/get-balances-address-address.md) | `/balances/address/{address}` | Get token balances for an address | chains, address | | [GET](https://intel.arkm.com/llms/get-balances-entity-entity.md) | `/balances/entity/{entity}` | Get token balances for an entity | cheap, chains, entity | | [GET](https://intel.arkm.com/llms/get-balances-solana-subaccounts-address-addresses.md) | `/balances/solana/subaccounts/address/{addresses}` | Get Solana subaccount balances for addresses | pricingID, limit, addresses | | [GET](https://intel.arkm.com/llms/get-balances-solana-subaccounts-entity-entities.md) | `/balances/solana/subaccounts/entity/{entities}` | Get Solana subaccount balances for entities | pricingID, limit, entities | | [GET](https://intel.arkm.com/llms/get-chains.md) | `/chains` | Get supported chains list | | | [GET](https://intel.arkm.com/llms/get-cluster-id-summary.md) | `/cluster/{id}/summary` | Get cluster summary statistics | id | | [GET](https://intel.arkm.com/llms/get-counterparties-address-address.md) | `/counterparties/address/{address}` | Get top counterparties for an address | base, chains, flow, from | | [GET](https://intel.arkm.com/llms/get-counterparties-entity-entity.md) | `/counterparties/entity/{entity}` | Get top counterparties for an entity | base, chains, flow, from | | [GET](https://intel.arkm.com/llms/get-flow-address-address.md) | `/flow/address/{address}` | Get historical USD flows for an address | chains, address | | [GET](https://intel.arkm.com/llms/get-flow-entity-entity.md) | `/flow/entity/{entity}` | Get historical USD flows for an entity | chains, entity | | [GET](https://intel.arkm.com/llms/get-history-address-address.md) | `/history/address/{address}` | Get historical data for an address | chains, address | | [GET](https://intel.arkm.com/llms/get-history-entity-entity.md) | `/history/entity/{entity}` | Get historical data for an entity | chains, entity | | [POST](https://intel.arkm.com/llms/post-intelligence-address-batch.md) | `/intelligence/address/batch` | Batch lookup address intelligence | chains, chain | | [POST](https://intel.arkm.com/llms/post-intelligence-address-batch-all.md) | `/intelligence/address/batch/all` | Batch lookup address intelligence across all chains | chains, chain | | [GET](https://intel.arkm.com/llms/get-intelligence-address-address.md) | `/intelligence/address/{address}` | Get intelligence about an address | chain, address | | [GET](https://intel.arkm.com/llms/get-intelligence-address-address-all.md) | `/intelligence/address/{address}/all` | Get all intelligence about an address across chains | address | | [POST](https://intel.arkm.com/llms/post-intelligence-address_enriched-batch.md) | `/intelligence/address_enriched/batch` | Batch lookup enriched address intelligence | chains, includeTags, includeClusters, includeEntityPredictions | | [POST](https://intel.arkm.com/llms/post-intelligence-address_enriched-batch-all.md) | `/intelligence/address_enriched/batch/all` | Batch lookup enriched address intelligence across all chains | chains, includeTags, includeClusters, includeEntityPredictions | | [GET](https://intel.arkm.com/llms/get-intelligence-address_enriched-address.md) | `/intelligence/address_enriched/{address}` | Get intelligence about an address with additional address information | chain, includeTags, includeClusters, includeEntityPredictions | | [GET](https://intel.arkm.com/llms/get-intelligence-address_enriched-address-all.md) | `/intelligence/address_enriched/{address}/all` | Get intelligence about an address on all chains with additional address information | includeTags, includeEntityPredictions, includeClusters, address | | [GET](https://intel.arkm.com/llms/get-intelligence-address_tags-updates.md) | `/intelligence/address_tags/updates` | Get address-tag association updates | since, from, to, status | | [GET](https://intel.arkm.com/llms/get-intelligence-addresses-updates.md) | `/intelligence/addresses/updates` | Get address intelligence updates | since, from, to, status | | [GET](https://intel.arkm.com/llms/get-intelligence-contract-chain-address.md) | `/intelligence/contract/{chain}/{address}` | Get intelligence about a contract | chain, address | | [GET](https://intel.arkm.com/llms/get-intelligence-entities-updates.md) | `/intelligence/entities/updates` | Get entity intelligence updates | since, from, to, status | | [GET](https://intel.arkm.com/llms/get-intelligence-entity-entity.md) | `/intelligence/entity/{entity}` | Get intelligence about an entity | entity | | [GET](https://intel.arkm.com/llms/get-intelligence-entity-entity-summary.md) | `/intelligence/entity/{entity}/summary` | Get entity summary statistics | entity | | [GET](https://intel.arkm.com/llms/get-intelligence-entity_balance_changes.md) | `/intelligence/entity_balance_changes` | Get entity balance changes | chains, entityTypes, entityIds, entityTags | | [GET](https://intel.arkm.com/llms/get-intelligence-entity_predictions-entity.md) | `/intelligence/entity_predictions/{entity}` | Get predictions for an entity | entity | | [GET](https://intel.arkm.com/llms/get-intelligence-entity_types.md) | `/intelligence/entity_types` | Get all entity types | | | [GET](https://intel.arkm.com/llms/get-intelligence-search.md) | `/intelligence/search` | Search addresses, entities, and tokens | query, filterLimits, filterOffsets | | [GET](https://intel.arkm.com/llms/get-intelligence-tags-updates.md) | `/intelligence/tags/updates` | Get tag definition updates | since, from, to, status | | [GET](https://intel.arkm.com/llms/get-intelligence-token-chain-address.md) | `/intelligence/token/{chain}/{address}` | Get intelligence on a token by chain/address | chain, address | | [GET](https://intel.arkm.com/llms/get-intelligence-token-id.md) | `/intelligence/token/{id}` | Get intelligence on a token by CoinGecko pricing ID | id | | [GET](https://intel.arkm.com/llms/get-loans-address-address.md) | `/loans/address/{address}` | Get loan/borrow positions for an address | chains, address | | [GET](https://intel.arkm.com/llms/get-loans-entity-entity.md) | `/loans/entity/{entity}` | Get loan/borrow positions for an entity | chains, entity | | [GET](https://intel.arkm.com/llms/get-marketdata-altcoin_index.md) | `/marketdata/altcoin_index` | Get Altcoin Index | | | [GET](https://intel.arkm.com/llms/get-networks-history-chain.md) | `/networks/history/{chain}` | Get historical data for a blockchain network | chain | | [GET](https://intel.arkm.com/llms/get-networks-status.md) | `/networks/status` | Get current status for all blockchain networks | | | [GET](https://intel.arkm.com/llms/get-portfolio-address-address.md) | `/portfolio/address/{address}` | Get address portfolio history | time, chains, address | | [GET](https://intel.arkm.com/llms/get-portfolio-entity-entity.md) | `/portfolio/entity/{entity}` | Get entity portfolio history | time, chains, entity | | [GET](https://intel.arkm.com/llms/get-portfolio-timeSeries-address-address.md) | `/portfolio/timeSeries/address/{address}` | Get daily time series data for an address's token | pricingId, chains, address | | [GET](https://intel.arkm.com/llms/get-portfolio-timeSeries-entity-entity.md) | `/portfolio/timeSeries/entity/{entity}` | Get daily time series data for an entity's token | pricingId, chains, entity | | [GET](https://intel.arkm.com/llms/get-swaps.md) | `/swaps` | Get swaps | base, chains, flow, tokens | | [GET](https://intel.arkm.com/llms/get-tag-id-params.md) | `/tag/{id}/params` | Get tag parameters | limit, offset, id | | [GET](https://intel.arkm.com/llms/get-tag-id-summary.md) | `/tag/{id}/summary` | Get tag summary statistics | id | | [GET](https://intel.arkm.com/llms/get-token-addresses-id.md) | `/token/addresses/{id}` | Get chain addresses for a token | id | | [GET](https://intel.arkm.com/llms/get-token-arkham_exchange_tokens.md) | `/token/arkham_exchange_tokens` | Get Arkham Exchange tokens | | | [GET](https://intel.arkm.com/llms/get-token-balance-chain-address.md) | `/token/balance/{chain}/{address}` | Get token balance for an entity or address, for a specific chain/address | entityID, address, chain, address | | [GET](https://intel.arkm.com/llms/get-token-balance-id.md) | `/token/balance/{id}` | Get token balance (all chains) for an entity or address | entityID, address, id | | [GET](https://intel.arkm.com/llms/get-token-holders-chain-address.md) | `/token/holders/{chain}/{address}` | Get top token holders by chain and address | groupByEntity, chain, address | | [GET](https://intel.arkm.com/llms/get-token-holders-id.md) | `/token/holders/{id}` | Get top token holders by pricing ID | groupByEntity, id | | [GET](https://intel.arkm.com/llms/get-token-market-id.md) | `/token/market/{id}` | Get current market data for a token | id | | [GET](https://intel.arkm.com/llms/get-token-price-history-chain-address.md) | `/token/price/history/{chain}/{address}` | Get token price history by chain and address | daily, chain, address | | [GET](https://intel.arkm.com/llms/get-token-price-history-id.md) | `/token/price/history/{id}` | Get token price history by pricing ID | daily, id | | [GET](https://intel.arkm.com/llms/get-token-price_change-id.md) | `/token/price_change/{id}` | Get token price change since a timestamp | pastTime, id | | [GET](https://intel.arkm.com/llms/get-token-top.md) | `/token/top` | Get top tokens by exchange activity | timeframe, orderByAgg, orderByDesc, orderByPercent | | [GET](https://intel.arkm.com/llms/get-token-top_flow-chain-address.md) | `/token/top_flow/{chain}/{address}` | Get top token flow | timeLast, chains, chain, address | | [GET](https://intel.arkm.com/llms/get-token-top_flow-id.md) | `/token/top_flow/{id}` | Get top flow for a token by pricing ID | timeLast, chains, id | | [GET](https://intel.arkm.com/llms/get-token-trending.md) | `/token/trending` | Get trending tokens | | | [GET](https://intel.arkm.com/llms/get-token-trending-id.md) | `/token/trending/{id}` | Get a single trending token by ID | id | | [GET](https://intel.arkm.com/llms/get-token-volume-chain-address.md) | `/token/volume/{chain}/{address}` | Get volume for a token by chain/address | timeLast, granularity, chain, address | | [GET](https://intel.arkm.com/llms/get-token-volume-id.md) | `/token/volume/{id}` | Get volume for a token by pricing ID | timeLast, granularity, id | | [GET](https://intel.arkm.com/llms/get-transfers.md) | `/transfers` | Get transfers | base, chains, flow, from | | [GET](https://intel.arkm.com/llms/get-transfers-histogram.md) | `/transfers/histogram` | Get a detailed histogram of transfers (API Only) | base, chains, flow, from | | [GET](https://intel.arkm.com/llms/get-transfers-histogram-simple.md) | `/transfers/histogram/simple` | Get a simple histogram of transfers (Public) | base, chains, flow, from | | [GET](https://intel.arkm.com/llms/get-transfers-tx-hash.md) | `/transfers/tx/{hash}` | Get transfers for a transaction | transferType, chain, hash | | [GET](https://intel.arkm.com/llms/get-tx-hash.md) | `/tx/{hash}` | Get transaction details | hash | | [GET](https://intel.arkm.com/llms/get-user-entities.md) | `/user/entities` | List all private entities | includeAddresses | | [PUT](https://intel.arkm.com/llms/put-user-entities-only_add-id.md) | `/user/entities/only_add/{id}` | Update a private entity | id | | [GET](https://intel.arkm.com/llms/get-user-entities-id.md) | `/user/entities/{id}` | Get a private entity by ID | id | | [GET](https://intel.arkm.com/llms/get-user-labels.md) | `/user/labels` | Get user labels | | | [POST](https://intel.arkm.com/llms/post-user-labels.md) | `/user/labels` | Create labels | | | [GET](https://intel.arkm.com/llms/get-volume-address-address.md) | `/volume/address/{address}` | Get transfer volume for an address | chains, address | | [GET](https://intel.arkm.com/llms/get-volume-entity-entity.md) | `/volume/entity/{entity}` | Get transfer volume for an entity | chains, entity | | [GET](https://intel.arkm.com/llms/get-ws-active_connections.md) | `/ws/active_connections` | Get active WebSocket connections | | | [GET](https://intel.arkm.com/llms/get-ws-session-info.md) | `/ws/session-info` | Get WebSocket pricing info | | | [GET](https://intel.arkm.com/llms/get-ws-sessions.md) | `/ws/sessions` | List WebSocket sessions | | | [POST](https://intel.arkm.com/llms/post-ws-sessions.md) | `/ws/sessions` | Create WebSocket session | | | [GET](https://intel.arkm.com/llms/get-ws-sessions-id.md) | `/ws/sessions/{id}` | Get WebSocket session status | id | | [DELETE](https://intel.arkm.com/llms/delete-ws-sessions-id.md) | `/ws/sessions/{id}` | Delete WebSocket session | id | | [GET](https://intel.arkm.com/llms/get-ws-transfers.md) | `/ws/transfers` | WebSocket transfer streaming | | --- # Code Examples ## Bash — arkham-cli ```bash #!/bin/bash # ============================================================================ # Arkham API CLI Demo # ============================================================================ # A command-line tool demonstrating Arkham Intelligence API capabilities # including REST endpoints and WebSocket streaming. # # Prerequisites: # - curl (for REST API calls) # - websocat (for WebSocket streaming) - https://github.com/vi/websocat # - jq (optional, for pretty JSON output) # # Usage: # export ARKHAM_API_KEY="your-api-key" # ./arkham-cli.sh [options] # # ============================================================================ set -e # Configuration ARKHAM_API_KEY="${ARKHAM_API_KEY:-}" ARKHAM_BASE_URL="${ARKHAM_BASE_URL:-https://api.arkm.com}" ARKHAM_WS_URL="${ARKHAM_WS_URL:-wss://api.arkm.com/ws/transfers}" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ============================================================================ # Helper Functions # ============================================================================ print_error() { echo -e "${RED}Error:${NC} $1" >&2 } print_success() { echo -e "${GREEN}$1${NC}" } print_info() { echo -e "${BLUE}$1${NC}" } print_warning() { echo -e "${YELLOW}$1${NC}" } check_api_key() { if [ -z "$ARKHAM_API_KEY" ]; then print_error "ARKHAM_API_KEY environment variable is not set." echo "" echo "Please set your API key:" echo " export ARKHAM_API_KEY=\"your-api-key-here\"" echo "" exit 1 fi } check_dependency() { if ! command -v "$1" &> /dev/null; then print_error "$1 is required but not installed." echo "" case "$1" in websocat) echo "Install websocat:" echo " macOS: brew install websocat" echo " Linux: cargo install websocat" echo " Or download from: https://github.com/vi/websocat/releases" ;; jq) echo "Install jq (optional, for pretty JSON):" echo " macOS: brew install jq" echo " Linux: apt-get install jq" ;; *) echo "Please install $1 to continue." ;; esac exit 1 fi } # Make API request api_request() { local endpoint="$1" local method="${2:-GET}" local response response=$(curl -s -w "\n%{http_code}" \ -X "$method" \ -H "API-Key: $ARKHAM_API_KEY" \ -H "Content-Type: application/json" \ "${ARKHAM_BASE_URL}${endpoint}") local http_code http_code=$(echo "$response" | tail -n1) local body body=$(echo "$response" | sed '$d') if [ "$http_code" -ge 400 ]; then print_error "API request failed (HTTP $http_code)" echo "$body" return 1 fi # Pretty print if jq is available if command -v jq &> /dev/null; then echo "$body" | jq . else echo "$body" fi } # ============================================================================ # Commands # ============================================================================ cmd_help() { cat << 'EOF' Arkham API CLI Demo Usage: ./arkham-cli.sh [options] Commands: test Test API connection (health check) chains List supported blockchains address Get intelligence for an address transfers [options] Get recent transfers counterparties Get counterparties for an address ws-stream [options] Stream real-time transfers via WebSocket help Show this help message Transfer Options: --chain Filter by blockchain (e.g., ethereum) --usd-gte Minimum USD value --limit Number of results (default: 10) WebSocket Options: --usd-gte Minimum USD value (default: 10000000). Must be >= 10M unless using --from, --to, or --tokens --chains Filter by chains (comma-separated) --from Source addresses or entity names (comma-separated) --to Destination addresses or entity names (comma-separated) --tokens Token symbols or addresses (comma-separated) Examples: ./arkham-cli.sh test ./arkham-cli.sh address 0x28C6c06298d514Db089934071355E5743bf21d60 ./arkham-cli.sh transfers --chain ethereum --usd-gte 100000 ./arkham-cli.sh counterparties 0x28C6c06298d514Db089934071355E5743bf21d60 ./arkham-cli.sh ws-stream ./arkham-cli.sh ws-stream --from binance --usd-gte 100000 ./arkham-cli.sh ws-stream --tokens USDT,USDC --chains ethereum Environment Variables: ARKHAM_API_KEY Your Arkham API key (required) ARKHAM_BASE_URL API base URL (default: https://api.arkm.com) ARKHAM_WS_URL WebSocket URL (default: wss://api.arkm.com/ws/transfers) EOF } cmd_test() { print_info "Testing API connection..." check_api_key # Test with chains endpoint (requires auth) local response response=$(curl -s -w "\n%{http_code}" \ -H "API-Key: $ARKHAM_API_KEY" \ "${ARKHAM_BASE_URL}/chains") local http_code http_code=$(echo "$response" | tail -n1) local body body=$(echo "$response" | sed '$d') if [ "$http_code" -eq 200 ]; then print_success "Connection successful!" echo "" echo "Supported chains:" if command -v jq &> /dev/null; then echo "$body" | jq -r '.[]' else echo "$body" fi else print_error "Connection failed (HTTP $http_code)" echo "$body" exit 1 fi } cmd_chains() { print_info "Fetching supported chains..." check_api_key api_request "/chains" } cmd_address() { local address="$1" if [ -z "$address" ]; then print_error "Address is required." echo "Usage: ./arkham-cli.sh address
" exit 1 fi print_info "Fetching intelligence for address: $address" check_api_key api_request "/intelligence/address/$address" } cmd_transfers() { local chain="" local usd_gte="" local limit="10" # Parse options while [[ $# -gt 0 ]]; do case "$1" in --chain) chain="$2" shift 2 ;; --usd-gte) usd_gte="$2" shift 2 ;; --limit) limit="$2" shift 2 ;; *) shift ;; esac done # Build query parameters local params="limit=$limit" # Default to Binance address for demo local base_addr="0x28C6c06298d514Db089934071355E5743bf21d60" params="$params&base=$base_addr" # Add time filter (last 1 hour by default - use shorter window for reliability) params="$params&timeLast=1h" if [ -n "$chain" ]; then params="$params&chain=$chain" fi if [ -n "$usd_gte" ]; then params="$params&usdGte=$usd_gte" fi print_info "Fetching transfers..." echo " Base: $base_addr (Binance)" echo " Time: Last 1 hour" [ -n "$chain" ] && echo " Chain: $chain" [ -n "$usd_gte" ] && echo " Min USD: \$$usd_gte" echo "" check_api_key api_request "/transfers?$params" } cmd_counterparties() { local address="$1" shift 2>/dev/null || true if [ -z "$address" ]; then print_error "Address is required." echo "Usage: ./arkham-cli.sh counterparties
" exit 1 fi print_info "Fetching counterparties for: $address" echo "" check_api_key api_request "/counterparties/address/$address?limit=20" } cmd_ws_stream() { local usd_gte="10000000" local chains="" local from_filter="" local to_filter="" local tokens="" # Parse options while [[ $# -gt 0 ]]; do case "$1" in --usd-gte) usd_gte="$2" shift 2 ;; --chains) chains="$2" shift 2 ;; --from) from_filter="$2" shift 2 ;; --to) to_filter="$2" shift 2 ;; --tokens) tokens="$2" shift 2 ;; *) shift ;; esac done # Validate filter requirements local has_address_filter=false [[ -n "$from_filter" || -n "$to_filter" || -n "$tokens" ]] && has_address_filter=true if [ "$has_address_filter" = "false" ] && [ "$usd_gte" -lt 10000000 ]; then print_error "Filter requirement not met!" echo "" echo "WebSocket filters must include at least one of:" echo " - --from (source addresses/entities)" echo " - --to (destination addresses/entities)" echo " - --tokens (token symbols/addresses)" echo " - --usd-gte >= 10000000 (10 million USD)" echo "" exit 1 fi check_api_key check_dependency websocat print_info "Connecting to WebSocket..." echo " URL: $ARKHAM_WS_URL" echo " Min USD: \$$usd_gte" [ -n "$chains" ] && echo " Chains: $chains" [ -n "$from_filter" ] && echo " From: $from_filter" [ -n "$to_filter" ] && echo " To: $to_filter" [ -n "$tokens" ] && echo " Tokens: $tokens" echo "" print_warning "Press Ctrl+C to stop streaming" print_info "Note: Initial transfers may take up to 90 seconds after subscribing" echo "" # Build filter JSON local filter_json="{\"usdGte\":$usd_gte" if [ -n "$chains" ]; then local chains_array chains_array=$(echo "$chains" | sed 's/,/","/g') filter_json="$filter_json,\"chains\":[\"$chains_array\"]" fi if [ -n "$from_filter" ]; then local from_array from_array=$(echo "$from_filter" | sed 's/,/","/g') filter_json="$filter_json,\"from\":[\"$from_array\"]" fi if [ -n "$to_filter" ]; then local to_array to_array=$(echo "$to_filter" | sed 's/,/","/g') filter_json="$filter_json,\"to\":[\"$to_array\"]" fi if [ -n "$tokens" ]; then local tokens_array tokens_array=$(echo "$tokens" | sed 's/,/","/g') filter_json="$filter_json,\"tokens\":[\"$tokens_array\"]" fi filter_json="$filter_json}" # Subscribe message local subscribe_msg="{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"filters\":$filter_json}}" print_info "Subscribing with filter:" echo "$subscribe_msg" | (command -v jq &> /dev/null && jq . || cat) echo "" echo "---" echo "" # Connect and stream # Use websocat with --header for auth and -n to keep connection open after sending echo "$subscribe_msg" | websocat --header="API-Key:$ARKHAM_API_KEY" -n "$ARKHAM_WS_URL" | while read -r line; do if command -v jq &> /dev/null; then echo "$line" | jq . else echo "$line" fi echo "---" done } # ============================================================================ # Main # ============================================================================ main() { local command="${1:-help}" shift 2>/dev/null || true case "$command" in test) cmd_test ;; chains) cmd_chains ;; address) cmd_address "$@" ;; transfers) cmd_transfers "$@" ;; counterparties) cmd_counterparties "$@" ;; ws-stream|ws|stream) cmd_ws_stream "$@" ;; help|--help|-h) cmd_help ;; *) print_error "Unknown command: $command" echo "" cmd_help exit 1 ;; esac } main "$@" ``` ## Python — arkham demo ```python #!/usr/bin/env python3 """ Arkham API Demo - WebSocket & REST Examples A demonstration tool showcasing Arkham Intelligence API capabilities including REST endpoints and real-time WebSocket streaming. Prerequisites: pip install websockets requests Usage: export ARKHAM_API_KEY="your-api-key" python arkham_demo.py [options] Examples: python arkham_demo.py test python arkham_demo.py chains python arkham_demo.py address 0x28C6c06298d514Db089934071355E5743bf21d60 python arkham_demo.py transfers --chain ethereum --usd-gte 100000 python arkham_demo.py counterparties 0x28C6c06298d514Db089934071355E5743bf21d60 python arkham_demo.py ws-stream --usd-gte 1000000 --chains ethereum """ import argparse import asyncio import json import os import signal import sys from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional # Check for optional dependencies try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False try: import websockets HAS_WEBSOCKETS = True except ImportError: HAS_WEBSOCKETS = False # ============================================================================ # Configuration # ============================================================================ DEFAULT_BASE_URL = "https://api.arkm.com" DEFAULT_WS_URL = "wss://api.arkm.com/ws/transfers" # Example addresses for demos EXAMPLE_ADDRESSES = { "binance": "0x28C6c06298d514Db089934071355E5743bf21d60", "vitalik": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", } # ============================================================================ # Utilities # ============================================================================ class Colors: """ANSI color codes for terminal output.""" RED = "\033[0;31m" GREEN = "\033[0;32m" YELLOW = "\033[1;33m" BLUE = "\033[0;34m" RESET = "\033[0m" @classmethod def disable(cls): """Disable colors (for non-TTY output).""" cls.RED = cls.GREEN = cls.YELLOW = cls.BLUE = cls.RESET = "" # Disable colors if not a TTY if not sys.stdout.isatty(): Colors.disable() def print_error(msg: str) -> None: """Print error message in red.""" print(f"{Colors.RED}Error:{Colors.RESET} {msg}", file=sys.stderr) def print_success(msg: str) -> None: """Print success message in green.""" print(f"{Colors.GREEN}{msg}{Colors.RESET}") def print_info(msg: str) -> None: """Print info message in blue.""" print(f"{Colors.BLUE}{msg}{Colors.RESET}") def print_warning(msg: str) -> None: """Print warning message in yellow.""" print(f"{Colors.YELLOW}{msg}{Colors.RESET}") def print_json(data: Any) -> None: """Pretty print JSON data.""" print(json.dumps(data, indent=2)) def get_api_key() -> str: """Get API key from environment or exit with error.""" api_key = os.environ.get("ARKHAM_API_KEY", "") if not api_key: print_error("ARKHAM_API_KEY environment variable is not set.") print("") print("Please set your API key:") print(' export ARKHAM_API_KEY="your-api-key-here"') print("") sys.exit(1) return api_key def check_requests() -> None: """Check if requests library is available.""" if not HAS_REQUESTS: print_error("requests library is required for REST API calls.") print("") print("Install it with:") print(" pip install requests") print("") sys.exit(1) def check_websockets() -> None: """Check if websockets library is available.""" if not HAS_WEBSOCKETS: print_error("websockets library is required for WebSocket streaming.") print("") print("Install it with:") print(" pip install websockets") print("") sys.exit(1) # ============================================================================ # REST API Client # ============================================================================ class ArkhamAPI: """REST API client for Arkham Intelligence.""" def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL): """Initialize the API client.""" check_requests() self.api_key = api_key self.base_url = base_url.rstrip("/") self.session = requests.Session() self.session.headers.update({ "API-Key": api_key, "Content-Type": "application/json", }) def _request(self, endpoint: str, method: str = "GET", **kwargs) -> Dict[str, Any]: """Make an API request.""" url = f"{self.base_url}{endpoint}" response = self.session.request(method, url, **kwargs) if response.status_code >= 400: print_error(f"API request failed (HTTP {response.status_code})") try: print_json(response.json()) except json.JSONDecodeError: print(response.text) sys.exit(1) return response.json() def test_connection(self) -> List[str]: """Test API connection by fetching supported chains.""" return self._request("/chains") def get_chains(self) -> List[str]: """Get list of supported blockchains.""" return self._request("/chains") def get_address(self, address: str) -> Dict[str, Any]: """Get intelligence for an address.""" return self._request(f"/intelligence/address/{address}") def get_transfers( self, base: Optional[str] = None, chain: Optional[str] = None, usd_gte: Optional[int] = None, time_last: str = "1h", limit: int = 10, ) -> Dict[str, Any]: """Get transfers with optional filters.""" params = {"limit": limit, "timeLast": time_last} if base: params["base"] = base if chain: params["chain"] = chain if usd_gte: params["usdGte"] = usd_gte return self._request("/transfers", params=params) def get_counterparties( self, address: str, limit: int = 20, ) -> Dict[str, Any]: """Get counterparties for an address.""" params = {"limit": limit} return self._request(f"/counterparties/address/{address}", params=params) # ============================================================================ # WebSocket Client # ============================================================================ class ArkhamWebSocket: """WebSocket streaming client for Arkham Intelligence.""" def __init__(self, api_key: str, ws_url: str = DEFAULT_WS_URL): """Initialize the WebSocket client.""" check_websockets() self.api_key = api_key self.ws_url = ws_url self.websocket = None self.session_id: Optional[str] = None self.filter_id: Optional[int] = None self._running = True self._message_id = 0 def _next_message_id(self) -> str: """Generate next message ID.""" self._message_id += 1 return str(self._message_id) async def connect(self) -> None: """Connect to the WebSocket server.""" headers = {"API-Key": self.api_key} self.websocket = await websockets.connect( self.ws_url, additional_headers=headers, ping_interval=30, ping_timeout=10, ) print_success("Connected to WebSocket") async def subscribe(self, filters: Dict[str, Any]) -> Dict[str, Any]: """Subscribe to transfer notifications with filters.""" if not self.websocket: raise RuntimeError("Not connected. Call connect() first.") message = { "id": self._next_message_id(), "type": "subscribe", "payload": {"filters": filters}, } print_info("Sending subscription:") print_json(message) print("") await self.websocket.send(json.dumps(message)) # Wait for acknowledgment response = await self.websocket.recv() data = json.loads(response) # ACK structure: {"type": "ack", "payload": {"success": true, "data": {"filterId": X, "sessionId": "..."}}} payload = data.get("payload", {}) if data.get("type") == "ack" and payload.get("success"): ack_data = payload.get("data", {}) self.session_id = ack_data.get("sessionId") self.filter_id = ack_data.get("filterId") print_success(f"Subscribed successfully!") print(f" Session ID: {self.session_id}") print(f" Filter ID: {self.filter_id}") print("") return data else: print_error("Subscription failed:") print_json(data) raise RuntimeError("Subscription failed") async def stream(self) -> None: """Stream transfer notifications.""" if not self.websocket: raise RuntimeError("Not connected. Call connect() first.") print_info("Streaming transfers (Press Ctrl+C to stop)...") print_info("Note: Initial transfers may take up to 90 seconds after subscribing") print("---") try: async for message in self.websocket: if not self._running: break data = json.loads(message) msg_type = data.get("type", "unknown") payload = data.get("payload", {}) if msg_type == "transfer": self._print_transfer(payload) elif msg_type == "error": error_code = payload.get("code", "UNKNOWN") error_msg = payload.get("message", "Unknown error") print_error(f"Server error [{error_code}]: {error_msg}") if error_code == "INSUFFICIENT_CREDITS": print_warning("You've run out of WebSocket credits.") break elif error_code == "TIER_RATE_LIMITED": reset_in = payload.get("resetIn", "?") print_warning(f"Rate limited. Resets in {reset_in} seconds.") elif msg_type == "ack": # Already handled in subscribe, but print if we get another print_info("Received ACK") print_json(data) print("---") else: # Print any other message types print_json(data) print("---") except websockets.exceptions.ConnectionClosed as e: print_warning(f"Connection closed: {e}") def _print_transfer(self, payload: Dict[str, Any]) -> None: """Pretty print a transfer notification. Payload structure from docs: { "transfer": { "fromAddress": {"address": "...", "arkhamEntity": {...}, "arkhamLabel": {...}}, "toAddress": {"address": "...", ...}, "tokenSymbol": "USDT", "historicalUSD": 503.9, "unitValue": 503.9, "chain": "ethereum", "transactionHash": "0x...", "blockTimestamp": "2025-08-28T11:01:35Z" }, "alertId": 49 } """ transfer = payload.get("transfer", {}) alert_id = payload.get("alertId") from_addr = transfer.get("fromAddress", {}) to_addr = transfer.get("toAddress", {}) # Get labels - prefer entity name, then label name, then truncated address from_entity = from_addr.get("arkhamEntity", {}) from_label_obj = from_addr.get("arkhamLabel", {}) from_label = from_entity.get("name") or from_label_obj.get("name") or from_addr.get("address", "Unknown")[:16] + "..." to_entity = to_addr.get("arkhamEntity", {}) to_label_obj = to_addr.get("arkhamLabel", {}) to_label = to_entity.get("name") or to_label_obj.get("name") or to_addr.get("address", "Unknown")[:16] + "..." # USD value is in historicalUSD field usd_value = transfer.get("historicalUSD", 0) unit_value = transfer.get("unitValue", 0) token_symbol = transfer.get("tokenSymbol", "???") chain = transfer.get("chain", "unknown") tx_hash = transfer.get("transactionHash", "N/A") timestamp = transfer.get("blockTimestamp", "") print(f"{Colors.GREEN}Transfer:{Colors.RESET} {from_label} -> {to_label}") print(f" Value: ${usd_value:,.2f} ({unit_value:,.4f} {token_symbol})") print(f" Chain: {chain}") print(f" Time: {timestamp}") print(f" Hash: {tx_hash[:42]}...") if alert_id: print(f" Alert ID: {alert_id}") print("---") async def disconnect(self) -> None: """Disconnect from the WebSocket server.""" self._running = False if self.websocket: await self.websocket.close() print_info("Disconnected from WebSocket") async def reconnect(self) -> Dict[str, Any]: """Reconnect using saved session ID.""" if not self.session_id: raise RuntimeError("No session ID available for reconnection") if not self.websocket: await self.connect() message = { "id": self._next_message_id(), "type": "reconnect", "payload": {"sessionId": self.session_id}, } await self.websocket.send(json.dumps(message)) response = await self.websocket.recv() return json.loads(response) # ============================================================================ # Command Handlers # ============================================================================ def cmd_test(args: argparse.Namespace) -> None: """Test API connection.""" print_info("Testing API connection...") api_key = get_api_key() api = ArkhamAPI(api_key) chains = api.test_connection() print_success("Connection successful!") print("") print("Supported chains:") for chain in chains: print(f" - {chain}") def cmd_chains(args: argparse.Namespace) -> None: """List supported chains.""" print_info("Fetching supported chains...") api_key = get_api_key() api = ArkhamAPI(api_key) chains = api.get_chains() print_json(chains) def cmd_address(args: argparse.Namespace) -> None: """Get address intelligence.""" print_info(f"Fetching intelligence for: {args.address}") api_key = get_api_key() api = ArkhamAPI(api_key) data = api.get_address(args.address) print_json(data) def cmd_transfers(args: argparse.Namespace) -> None: """Get transfers with filters.""" api_key = get_api_key() api = ArkhamAPI(api_key) # Default to Binance address for demo base = args.base or EXAMPLE_ADDRESSES["binance"] print_info("Fetching transfers...") print(f" Base: {base}") print(f" Time: Last 1 hour") if args.chain: print(f" Chain: {args.chain}") if args.usd_gte: print(f" Min USD: ${args.usd_gte:,}") print(f" Limit: {args.limit}") print("") data = api.get_transfers( base=base, chain=args.chain, usd_gte=args.usd_gte, limit=args.limit, ) print_json(data) def cmd_counterparties(args: argparse.Namespace) -> None: """Get counterparties for an address.""" api_key = get_api_key() api = ArkhamAPI(api_key) print_info(f"Fetching counterparties for: {args.address}") print("") data = api.get_counterparties(address=args.address) print_json(data) def cmd_ws_stream(args: argparse.Namespace) -> None: """Stream transfers via WebSocket.""" api_key = get_api_key() # Build filters filters: Dict[str, Any] = {"usdGte": args.usd_gte} if args.chains: filters["chains"] = [c.strip() for c in args.chains.split(",")] if args.from_filter: filters["from"] = [f.strip() for f in args.from_filter.split(",")] if args.to: filters["to"] = [t.strip() for t in args.to.split(",")] if args.tokens: filters["tokens"] = [t.strip() for t in args.tokens.split(",")] # Validate filter requirements has_address_filter = any(k in filters for k in ["from", "to", "tokens"]) if not has_address_filter and args.usd_gte < 10000000: print_error("Filter requirement not met!") print("") print("WebSocket filters must include at least one of:") print(" - --from (source addresses/entities)") print(" - --to (destination addresses/entities)") print(" - --tokens (token symbols/addresses)") print(" - --usd-gte >= 10000000 (10 million USD)") print("") sys.exit(1) print_info("WebSocket Configuration:") print(f" URL: {DEFAULT_WS_URL}") print(f" Min USD: ${args.usd_gte:,}") if args.chains: print(f" Chains: {args.chains}") if args.from_filter: print(f" From: {args.from_filter}") if args.to: print(f" To: {args.to}") if args.tokens: print(f" Tokens: {args.tokens}") print("") async def run_stream(): ws = ArkhamWebSocket(api_key) # Handle Ctrl+C gracefully def signal_handler(sig, frame): print("") print_warning("Interrupt received, disconnecting...") ws._running = False signal.signal(signal.SIGINT, signal_handler) try: await ws.connect() await ws.subscribe(filters) await ws.stream() except Exception as e: print_error(f"WebSocket error: {e}") finally: await ws.disconnect() asyncio.run(run_stream()) # ============================================================================ # Main # ============================================================================ def main() -> None: """Main entry point.""" parser = argparse.ArgumentParser( description="Arkham API Demo - WebSocket & REST Examples", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python arkham_demo.py test python arkham_demo.py chains python arkham_demo.py address 0x28C6c06298d514Db089934071355E5743bf21d60 python arkham_demo.py transfers --chain ethereum --usd-gte 100000 python arkham_demo.py counterparties 0x28C6c06298d514Db089934071355E5743bf21d60 --days 30 python arkham_demo.py ws-stream --usd-gte 1000000 --chains ethereum Environment Variables: ARKHAM_API_KEY Your Arkham API key (required) """, ) subparsers = parser.add_subparsers(dest="command", help="Available commands") # test command subparsers.add_parser("test", help="Test API connection") # chains command subparsers.add_parser("chains", help="List supported blockchains") # address command address_parser = subparsers.add_parser("address", help="Get intelligence for an address") address_parser.add_argument("address", help="Blockchain address to look up") # transfers command transfers_parser = subparsers.add_parser("transfers", help="Get recent transfers") transfers_parser.add_argument("--base", help="Base address to filter by") transfers_parser.add_argument("--chain", help="Blockchain to filter by") transfers_parser.add_argument("--usd-gte", type=int, help="Minimum USD value") transfers_parser.add_argument("--limit", type=int, default=10, help="Number of results (default: 10)") # counterparties command cp_parser = subparsers.add_parser("counterparties", help="Get counterparties for an address") cp_parser.add_argument("address", help="Address to get counterparties for") # ws-stream command ws_parser = subparsers.add_parser("ws-stream", help="Stream real-time transfers via WebSocket") ws_parser.add_argument("--usd-gte", type=int, default=10000000, help="Minimum USD value (default: 10000000). Must be >= 10M unless using --from/--to/--tokens") ws_parser.add_argument("--chains", help="Comma-separated list of chains to filter") ws_parser.add_argument("--from", dest="from_filter", help="Comma-separated source addresses or entity names") ws_parser.add_argument("--to", help="Comma-separated destination addresses or entity names") ws_parser.add_argument("--tokens", help="Comma-separated token symbols or addresses") args = parser.parse_args() if not args.command: parser.print_help() sys.exit(0) # Dispatch to command handler commands = { "test": cmd_test, "chains": cmd_chains, "address": cmd_address, "transfers": cmd_transfers, "counterparties": cmd_counterparties, "ws-stream": cmd_ws_stream, } handler = commands.get(args.command) if handler: handler(args) else: print_error(f"Unknown command: {args.command}") parser.print_help() sys.exit(1) if __name__ == "__main__": main() ``` ## Python — rest example ```python #!/usr/bin/env python3 """ Arkham API - REST Example Shows how to make a simple REST API call to get address intelligence. Usage: export ARKHAM_API_KEY="your-api-key" python rest_example.py """ import os import requests API_KEY = os.environ.get("ARKHAM_API_KEY", "") if not API_KEY: print("Error: Set ARKHAM_API_KEY environment variable") print(' export ARKHAM_API_KEY="your-api-key"') exit(1) # Example: Get intelligence for Binance hot wallet address = "0x28C6c06298d514Db089934071355E5743bf21d60" response = requests.get( f"https://api.arkm.com/intelligence/address/{address}", headers={"API-Key": API_KEY} ) if response.ok: import json print(json.dumps(response.json(), indent=2)) else: print(f"Error {response.status_code}: {response.text}") ``` ## Python — websocket example ```python #!/usr/bin/env python3 """ Arkham API - WebSocket Streaming Example Streams real-time transfer notifications via WebSocket. Press Ctrl+C to stop. Requirements: pip install websockets Usage: export ARKHAM_API_KEY="your-api-key" python websocket_example.py """ import asyncio import json import os import signal import ssl import websockets # WARNING: This disables SSL verification for easier local testing. # For production use, remove these lines and use default SSL verification. ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE API_KEY = os.environ.get("ARKHAM_API_KEY", "") if not API_KEY: print("Error: Set ARKHAM_API_KEY environment variable") print(' export ARKHAM_API_KEY="your-api-key"') exit(1) running = True def handle_signal(sig, frame): global running print("\nDisconnecting...") running = False signal.signal(signal.SIGINT, handle_signal) async def stream_transfers(): url = "wss://api.arkm.com/ws/transfers" headers = {"API-Key": API_KEY} async with websockets.connect(url, additional_headers=headers, ssl=ssl_context) as ws: # Subscribe to transfers from CEXs >= $10k subscribe = { "id": "1", "type": "subscribe", "payload": { "filters": { "from": ["type:cex"], "usdGte": 10000 } } } await ws.send(json.dumps(subscribe)) # Wait for ACK ack = await ws.recv() print("Subscribed:", json.loads(ack)) print("Waiting for transfers (may take up to 90 seconds)...") print("Press Ctrl+C to stop\n---") # Stream transfers async for message in ws: if not running: break data = json.loads(message) if data.get("type") == "transfer": t = data["payload"]["transfer"] print(f"${t['historicalUSD']:,.0f} {t['tokenSymbol']} on {t['chain']}") print(f" {t['transactionHash'][:20]}...") print("---") asyncio.run(stream_transfers()) ``` ## Postman Collections - [Arkham-API-Complete.postman_collection](https://intel.arkm.com/cookbook/postman/Arkham-API-Complete.postman_collection.json) - [Arkham-API-Production.postman_environment](https://intel.arkm.com/cookbook/postman/Arkham-API-Production.postman_environment.json)