Data API Overview
About
The Surf Data API provides typed REST endpoints for crypto market data, project analytics, on-chain queries, and more. Endpoints are billed per request in credits and rate-limited per API key — see Rate Limits and Credits below.
Base URL: https://api.asksurf.ai/gateway
Authentication
All data endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <API_KEY>
How to get your API key
- Go to agents.asksurf.ai and sign in with Google or email
- Open the Dashboard and click Generate API Key
- Copy your key (starts with
sk-)
Free tier
- No sign-up required. Every IP gets 30 free credits per day — try any endpoint without an API key. Resets at 00:00 UTC.
- Sign up for full access. Generate an API key from the Dashboard, then top up with credit card or crypto (USDC).
Using the key
In code (Data API / Chat API):
curl "https://api.asksurf.ai/gateway/v1/market/price?symbol=BTC" \
-H "Authorization: Bearer sk-your-key-here"In the CLI:
surf auth --api-key sk-your-key-here # saved to OS keychain (or ~/.surf/config.json as fallback)
# or
export SURF_API_KEY=sk-your-key-hereIn AI agents (Claude Code, Cursor, etc.): the Surf Skill picks up SURF_API_KEY from your environment automatically. No extra config needed.
Rate Limits
Rate limits are enforced per API key as a sliding 60-second window. Each account holds one bucket regardless of how many keys it has issued.
| Tier | Limit | Applies to |
|---|---|---|
| Anonymous (no API key) | 30 credits / day per IP | Unauthenticated trial requests. Daily credit budget — there is no separate per-second cap; once the 30 credits are spent the IP is blocked until 00:00 UTC. |
| Pay-as-you-go (default) | 100 requests / minute per API key | Auto-assigned to every account on sign-up. |
| Enterprise / Custom | Configured per contract | Negotiated based on your expected peak throughput. Contact us to raise this. |
A few specifics:
- No per-endpoint cap. Every request — regardless of endpoint — counts against the same per-API-key bucket.
- Burst-friendly. Because the limiter is a sliding 60-second window, you can fire your full quota at once (e.g. all 100 requests in the first second) and then wait for the window to roll forward. There is no separate concurrency cap.
- Credit cost is independent. Different endpoints cost different amounts of credits — see
meta.credits_usedon every response. Credits gate your spend; the RPM limit gates your throughput. They are not linked.
When you exceed the limit you get HTTP 429 with error.code: RATE_LIMITED and a message like rate limit exceeded (100 requests per 60s). Back off and retry — the window is continuous, so capacity returns as old requests age out.
Parameter Conventions
Asset Identification
| Param | Type | Description | Example |
|---|---|---|---|
symbol | string | Uppercase ticker symbol(s), comma-separated | BTC, ETH,SOL |
q | string | Free-text search keyword | bitcoin |
id | string | Surf entity UUID for direct lookup | 550e8400-... |
address | string | On-chain contract or wallet address. Also accepts ENS names (e.g. vitalik.eth) | 0xdead... |
Time & Aggregation
| Param | Type | Description | Example |
|---|---|---|---|
time_range | string or int | Lookback window | 7d, 30d, 365d, max |
interval | string | Candlestick / indicator interval | 1h, 1d, 1w |
granularity | string | Data aggregation granularity | day, block, hour |
Filtering & Sorting
| Param | Type | Description | Example |
|---|---|---|---|
chain | string | Blockchain name (canonical long-form only) | ethereum, solana, base |
currency | string | Quote currency | usd, eur, btc |
metric | string | Metric name (endpoint-specific enum) | nupl, tvl |
type | string | Category selector (endpoint-specific enum) | us-btc-spot |
sort_by | string | Sort field | volume_24h, market_cap |
order | string | Sort direction | asc, desc |
Pagination
All list endpoints support:
limit(int, default: 20, max: 100) — results per pageoffset(int, default: 0) — pagination offset
Supported Chains
Use canonical long-form names only. Short aliases (eth, sol, matic, etc.) are not accepted.
| Chain | Value |
|---|---|
| Ethereum | ethereum |
| Polygon | polygon |
| BNB Chain | bsc |
| Solana | solana |
| Avalanche | avalanche |
| Arbitrum | arbitrum |
| Optimism | optimism |
| Fantom | fantom |
| Base | base |
| Linea | linea |
| Cyber | cyber |
Not all chains are available on every endpoint. Check each endpoint's chain enum for supported values.
Endpoint Notes
Quick reference for data characteristics across endpoint groups. For raw SQL access to the underlying tables, see Onchain SQL.
Exchange
CEX market data from major exchanges. Real-time or near-real-time.
pricereturns last traded price;klinesreturns OHLCV candlesticksdepthis a live order book snapshot — not historicalfunding-historyandlong-short-ratioare perp-specific
Market
Aggregated market indicators and rankings.
pricereturns CoinGecko-sourced token prices — hourly snapshots, not tick-levelrankingsorts by market cap or volume; usesort_byandorderto customizeonchain-indicatorcovers NUPL, SOPR, MVRV and other BTC-centric metricsetfreturns daily fund flow data for US BTC/ETH spot ETFs
Token
On-chain token activity. Data refreshes daily (~24h lag).
dex-trades: swap events from Uniswap, SushiSwap, Curve, Balancer. USD amounts are one-sided (token_sold value, following DefiLlama methodology). Chains: Ethereum, Base.transfers: ERC-20 + native token transfers with USD pricing. Chains: Ethereum, Base, Arbitrum, BSC, Tron.holders: top holders by balance. Snapshot, not real-time.- No aggregator attribution — a 1inch-routed swap shows as the underlying DEX.
holders,transfers, anddex-tradessupport opt-in address-label enrichment viainclude=labels— see Address Label Enrichment.
Project
Protocol-level DeFi data.
defi-metrics: TVL, fees, revenue, yields per protocol. TVL is supply-side only (excludes borrows). CoinGecko-priced — unpriced tokens valued at $0.defi-ranking: sorted protocol list with TVL, fee, and volume data.
News
Aggregated crypto news from multiple sources.
feedreturns latest articles;detailreturns full content by article ID
Wallet
Multi-chain wallet analytics.
detailreturns wallet balance breakdown across chainslabels-batchreturns known wallet labels (exchange, fund, whale, etc.)protocolsshows DeFi positions held by the walletnet-worthreturns historical net worth (daily granularity)transfersandhistorysupport opt-in address-label enrichment viainclude=labels— see Address Label Enrichment.
Onchain
Direct access to on-chain data including raw SQL queries.
tx: single EVM transaction lookup by hash (JSON-RPC). Supports opt-in address-label enrichment viainclude=labels— see Address Label Enrichment.sql: execute arbitrary SQL against 106 ClickHouse tables. See the Onchain SQL guide for table schemas and query patterns.query: structured (no-SQL) query interface for the same tablesschema: returns all available table metadata programmatically — use this for dynamic discoverybridge-ranking: cross-chain bridge volume rankingsyield-ranking: DeFi pool yields sorted by APY
Prediction Market
Polymarket (on-chain, Polygon) and Kalshi (centralized API) data.
- All volume figures are one-sided (Paradigm methodology)
- Polymarket data lag: ~1 hour. Kalshi data lag: ~6 hours.
- Polymarket open interest is sparse — only rows on days with OI-changing events, not every calendar day
positionsreturns per-wallet position data with PnLrankingreturns pre-computed market rankings by volume and OI
Fund
Crypto venture fund data.
detailreturns fund profile (AUM, portfolio size, investment stage)portfolioreturns fund holdings with entry datesrankingsorts funds by AUM or deal count
Search
Full-text search across all entity types. All search endpoints accept a q parameter.
Web
fetch: retrieves and parses a web page. Useful for enrichment workflows.
Address Label Enrichment
Six endpoints accept an optional include=labels query parameter that attaches entity-label records to every address in the response. Labels come from Surf's address intelligence service and cover exchanges, protocols, funds, market makers, notable wallets, and ENS-registered addresses.
Supported endpoints:
| Endpoint | Address field(s) enriched | Attached field(s) |
|---|---|---|
GET /v1/wallet/transfers | from_address, to_address | from_label, to_label |
GET /v1/wallet/history | from_address, to_address | from_label, to_label |
GET /v1/token/holders | address | label (full record) |
GET /v1/token/transfers | from_address, to_address | from_label, to_label |
GET /v1/token/dex-trades | taker | taker_label |
GET /v1/onchain/tx | from, to | from_label, to_label |
Usage:
curl "https://api.asksurf.ai/gateway/v1/wallet/transfers?\
address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&\
chain=ethereum&limit=5&include=labels" \
-H "Authorization: Bearer $SURF_API_KEY"Example response item (wallet transfer to vitalik.eth with include=labels):
{
"tx_hash": "0x...",
"from_address": "0x94a42db1e578eff403b1644fa163e523803241fd",
"from_label": {
"address": "0x94a42db1e578eff403b1644fa163e523803241fd",
"labels": [{ "label": "jizzinmy.eth", "confidence": 1 }]
},
"to_address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"to_label": {
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"labels": [{ "label": "vitalik.eth", "confidence": 1 }],
"entity_name": "Vitalik Buterin",
"entity_type": "individual"
},
"amount": "0.0001",
"amount_usd": 0.23
}Semantics:
- Additive by default. Without
include=labels, responses are byte-identical to the pre-feature shape — the*_labelfields are absent, notnull. - Addresses with no known labels serialize as
nullon opt-in (not as empty objects). Filter withif (item.from_label)client-side. - Token holders are a special case:
entity_nameandentity_typeare populated unconditionally for backward compatibility. The richerlabelrecord is only attached wheninclude=labelsis set. - Latency impact is small. Labels are cached per-address (~1h) and a 200ms timeout wraps every enrichment call — a slow or failing label backend returns items without labels rather than failing the request.
- Unknown
includevalues return HTTP 422 listing the valid options.
Error Responses
All endpoints return errors in a consistent format:
{
"error": {
"code": "PAID_BALANCE_ZERO",
"message": "credits exhausted; top up to continue"
}
}HTTP Status
| Status | Meaning |
|---|---|
400 | Bad request — invalid or missing parameters |
401 | Unauthorized — missing or invalid API key |
402 | Payment required — credits exhausted (see error.code for which pool) |
404 | Not found — the requested entity does not exist |
422 | Validation error — parameter value is out of range or invalid |
429 | Rate limited — back off and retry. See Rate Limits. |
502 | Upstream error — a data source is temporarily unavailable |
503 | Upstream unavailable — retryable |
504 | Upstream timeout — retryable |
Error Codes
The error.code field gives a stable machine-readable classification. Switch on error.code, not on the message.
| Code | HTTP | When |
|---|---|---|
INVALID_REQUEST | 400 / 422 | Missing required param or invalid value |
INVALID_METRIC / INVALID_SOURCE / INVALID_FIELD / INVALID_OPERATOR | 422 | Enum or query-shape validation failure |
UNAUTHORIZED | 401 | Missing or invalid API key |
FREE_QUOTA_EXHAUSTED | 402 | Anonymous IP used all 30 daily free credits — sign up and use an API key to continue |
PAID_BALANCE_ZERO | 402 | Authenticated account balance is zero — top up in the Dashboard |
INSUFFICIENT_CREDIT | 402 | Legacy; older deployments may still return this. New clients should handle FREE_QUOTA_EXHAUSTED and PAID_BALANCE_ZERO |
NOT_FOUND | 404 | Entity / resource does not exist |
RATE_LIMITED | 429 | Per-key RPM exceeded — retry after the Retry-After header |
UPSTREAM_RATE_LIMITED | 502 | Data source returned 429 — retryable |
UPSTREAM_UNAVAILABLE | 503 | Data source temporarily down — retryable |
UPSTREAM_TIMEOUT | 504 | Data source timed out — retryable |
UPSTREAM_ERROR | 502 | Generic upstream failure (catch-all) |
QUERY_TIMEOUT | 504 | ClickHouse query exceeded the time limit (on POST /onchain/sql) — narrow the query |
INTERNAL_ERROR | 500 | Server bug — report to support |
Credits
Each API call costs a certain number of credits. The cost is returned in every response under meta.credits_used. Check your remaining balance via GET /v1/me/credit-balance.
Data API endpoints are priced in three tiers — Light (1 credit), Standard (2 credits), and Heavy (4 credits) — and re-calling the same endpoint within 3 minutes is free. For the full per-endpoint breakdown and what a credit costs in USD, see Credits & Pricing.