Developers
Wallet Score API
Pre-computed wallet analytics and the 0–100 zkCodex score over a single REST endpoint.
Updated 2026-10-10
Overview
The Wallet Analytics & Score API returns aggregated, scored onchain wallet intelligence: transaction metrics, activity streaks, DeFi breakdown, token diversity, earned badges, the 0–100 zkCodex score from the same engine as the site, and the wallet's rank among every wallet zkCodex has scored on that network. No raw log parsing is needed on your side.
Get wallet score and analytics
GET /api/v1/wallet/{network}/{address}| Parameter | In | Required | Description |
|---|---|---|---|
| network | path | yes | Network slug (base, arc, arbitrum, ethereum…) or its numeric chain ID (8453 is the same as base). See Supported networks. |
| address | path | yes | 42-character EVM hex address (0x…) |
The path is the whole request: there are no query parameters. A URL with a query string answers 308 with the bare URL in Location, so every request for a wallet shares the same cached answer.
Examples
curl -X GET "https://zkcodex.com/api/v1/wallet/base/0xEAa823AB4C4eE00283d8ed7be713ddf8A5ba0Fac" \n -H "Accept: application/json" \n --max-time 65async function getWalletScore(network: string, address: string) {
const res = await fetch(`https://zkcodex.com/api/v1/wallet/${network}/${address}`, {
headers: { Accept: "application/json" },
// A wallet the cache does not hold is scanned for up to 55 s.
signal: AbortSignal.timeout(65_000),
});
if (res.status === 429) {
throw new Error(`Rate limited; retry in ${res.headers.get("Retry-After")} s`);
}
const body = await res.json();
if (!body.success) throw new Error(body.error);
return body;
}
// A network slug or its chain ID: "base" and "8453" are the same.
const base = await getWalletScore("base", "0xEAa823AB4C4eE00283d8ed7be713ddf8A5ba0Fac");
console.log(`Base score: ${base.data.score.total} [${base.data.score.tier}]`);import requests
def get_wallet_score(network: str, address: str):
url = f"https://zkcodex.com/api/v1/wallet/{network}/{address}"
# A wallet the cache does not hold is scanned for up to 55 s.
res = requests.get(url, headers={"Accept": "application/json"}, timeout=65)
if res.status_code == 429:
raise RuntimeError(f"Rate limited; retry in {res.headers.get('Retry-After')} s")
body = res.json()
if not body["success"]:
raise RuntimeError(body["error"])
return body
data = get_wallet_score("base", "0xEAa823AB4C4eE00283d8ed7be713ddf8A5ba0Fac")
print(f"Score: {data['data']['score']['total']} ({data['data']['score']['tier']})")Response
{
"success": true,
"data": {
"address": "0xEAa823AB4C4eE00283d8ed7be713ddf8A5ba0Fac",
"network": "base",
"networkName": "Base",
"chainId": 8453,
"nativeCurrency": "ETH",
"isTestnet": false,
"score": {
"version": "score-2.1",
"total": 100,
"tier": "Legend",
"rank": 214,
"ranked": 2548,
"percentile": "Top 8.4%",
"breakdown": {
"activityScore": 88.67,
"volumeScore": 96,
"diversityScore": 100,
"technicalScore": 92.5
}
},
"metrics": {
"totalTransactions": 2412,
"uniqueContracts": 733,
"totalVolumeUSD": 223184.26,
"totalGasSpentNative": 0.03738,
"totalGasSpentUSD": 92.7,
"firstTransactionDate": "2023-07-15T12:37:35.000Z",
"lastTransactionDate": "2026-08-24T20:08:29.000Z"
},
"activity": {
"activeDays": 473,
"activeWeeks": 145,
"activeMonths": 38,
"currentStreakDays": 1,
"longestStreakDays": 26
},
"defi": {
"totalDeFiActivities": 482,
"swapCount": 215,
"liquidityCount": 42,
"stakingCount": 18,
"lpStakingCount": 6,
"deployedContracts": 8,
"nftMintCount": 145
},
"tokens": {
"uniqueTokens": 86,
"tokenTransfers": 1240,
"tokenDiversity": 68
},
"behavior": {
"transactionFrequency": 2.14,
"consistencyScore": 0.31
},
"badges": {
"totalEarned": 58,
"totalAvailable": 69,
"items": [
{ "id": "first-steps", "numericId": 1, "name": "First Steps", "tier": "bronze", "category": "Transactions", "description": "Made your first transaction on-chain", "earned": true },
{ "id": "base-pioneer", "numericId": 54, "name": "Base Pioneer", "tier": "base", "category": "Base Exclusive", "description": "Made your first transaction on Base", "earned": true }
]
}
},
"cached": false,
"timestamp": "2026-08-25T12:00:00.000Z"
}| Field | Meaning |
|---|---|
| score.version | The score model the number comes from (score-2.1). Scores of different versions are not compared. |
| score.total | 0–100 with one decimal, the same number the site and the Score NFT show for this wallet and network. |
| score.tier | Newbie (under 15), Bronze, Silver (35+), Gold (55+), Master (75+) or Legend (90+): the Score NFT's boundaries. |
| score.rank | The wallet's place among the wallets scored on this network, 1 being the best. null until at least 25 wallets are ranked on the network, and for a wallet left out of the ranking. |
| score.ranked | How many wallets are ranked on this network: a count, given before ranks open too. null only when the ranking could not be read for this answer. |
| score.percentile | "Top 8.4%": rank divided by ranked, rounded up to one decimal, from the real board. null whenever rank is null. |
| score.breakdown | The four category scores (0–100): activityScore is Activity Consistency, volumeScore DeFi Strategy, diversityScore Ecosystem Engagement, technicalScore Technical Prowess. |
| defi | Counts of the wallet's swaps, liquidity, staking and LP staking (lpStakingCount) actions, contracts deployed and NFT mints on this network. |
| behavior.transactionFrequency | Transactions the wallet sent per day between its first and its last one. |
| behavior.consistencyScore | 0–1: how evenly the wallet's transactions spread over its active days (1 is the same count every day). A wallet with a few very busy days scores near 0 however regular it is; activity.activeDays and the streaks tell how often it is active. |
| badges.items | The earned badges only: id, numericId (the badge's number, 1–69), name, tier, category, description and earned (always true). |
| cached | Always false; kept for older clients. A CDN copy repeats the original body, so timestamp (when the answer was computed) tells how old it is. |
Only complete scans score
When an explorer returns only part of the wallet's history, the answer is 503 with success: false and no score, never a 0 or an estimate. A complete scan is also stored and ranked, as a scan on the site is.
Limits and errors
| Limit | Detail |
|---|---|
| Rate limit | 6 scans a minute per IP address (an IPv6 address counts by its /64), counted across all of zkCodex's servers. Answers served from the CDN cache, redirects and 400s do not count. |
| Health check | GET /api/health takes 60 requests a minute per IP address on each server. Its report is cached at the edge for 60 s, and answers from that cache do not count. |
| Rate-limit headers | A 429, and an error after the scan was counted (500, 503, 504), carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the minute window ends) next to Retry-After. A 429 from /api/health carries them too. |
| Cached answers | A 200, a 308 and the /api/health report carry no RateLimit-* counters: one CDN copy is served to every caller, so it never shows one caller's numbers. |
| Cache | A 200 is kept at the edge for 60 s (then served stale for up to 120 s while it refreshes). Errors are never cached (Cache-Control: private, no-store). |
| Scan time | A wallet the cache does not hold is scanned for up to 55 s, the same budget as a scan on the site. Set your client timeout above that (65 s in the examples). |
| Query strings | Any query string answers 308 to the bare URL, so cache busters and parameters such as ?fresh=true do not start a new scan. |
| CORS | Access-Control-Allow-Origin: * on every answer, with Retry-After and the RateLimit-* headers exposed to browser callers. No cookies or credentials are read. |
| Status | When |
|---|---|
| 200 | The scan was complete: success: true with data. |
| 308 | The URL had a query string. Location holds the bare URL; follow it. |
| 400 | The address is not a 42-character 0x address, or the network is not in Supported networks. |
| 429 | More than 6 scans in a minute from your IP address. The Retry-After header gives the seconds to wait; RateLimit-Remaining is 0. |
| 500 | The scan failed unexpectedly. |
| 503 | An explorer returned only part of the wallet's history (or zkCodex's shared explorer allowance for public scans is used up for now), so there is no score. Try again later. |
| 504 | The scan ran past its 55 s budget. Try again later; a wallet with a very long history may need a few tries. |
{
"success": false,
"error": "Too many wallet scans; try again in a minute"
}Supported networks
Use the slug or the chain ID in the path: /api/v1/wallet/base/0x… and /api/v1/wallet/8453/0x… return the same wallet. Any other network answers 400.
| Network | Slug | Chain ID | Data source |
|---|---|---|---|
| Base | base | 8453 | Etherscan V2 |
| Monad | monad | 143 | Etherscan V2 |
| Arbitrum | arbitrum | 42161 | Etherscan V2 |
| Optimism | optimism | 10 | Etherscan V2 |
| Celo | celo | 42220 | Etherscan V2 |
| Ethereum | ethereum | 1 | Etherscan V2 |
| Unichain | unichain | 130 | Etherscan V2 |
| Ink | kraken | 57073 | Blockscout |
| Polygon | polygon | 137 | Etherscan V2 |
| ZKsync | zksync | 324 | Blockscout |
| Worldchain | worldchain | 480 | Etherscan V2 |
| Soneium | soneium | 1868 | Blockscout |
| Avalanche | avalanche | 43114 | Etherscan V2 |
| Linea | linea | 59144 | Etherscan V2 |
| Shape | shape | 360 | Blockscout |
| Base Sepolia | basesepolia | 84532 | Etherscan V2 |
| Plume | plume | 98866 | Blockscout |
| Scroll | scroll | 534352 | Blockscout |
| Katana | katana | 747474 | Etherscan V2 |
| XRPL EVM | xrpl | 1440000 | Blockscout |
| HyperEVM | hyperevm | 999 | Etherscan V2 |
| BNB Smart Chain | bsc | 56 | Etherscan V2 |
| Abstract | abstract | 2741 | Etherscan V2 |
| GIWA Testnet | giwa | 91342 | Blockscout |
| Arc | arc | 5042 | Etherscan V2 |
| Arc Testnet (Archived) | arc-testnet | 5042002 | Blockscout |
| Robinhood Chain | robinhood | 4663 | Etherscan V2 |