API
How to call Starkscan REST—base URL, auth, lists, errors. Paths live in the API reference.
API
The explorer, SDK, and CLI share one REST contract. For every path and field, open the API reference or starkscan-openapi.yaml. This page is how to use the API: URL shape, keys, paging, mistakes we see in the wild.
Base URL
Hosted external lane:
- Production base =
https://api.starkscan.co - Optional override:
STARKSCAN_BASE_URL=https://<custom-host> - Resources =
/v1/...→ example:https://api.starkscan.co/v1/SN_MAIN/status - Do not use the same-origin
/v1/...explorer lane for external integrations; it is reserved for app traffic on Starkscan-hosted pages.
Explorer
Same objects as JSON: Dashboard · Transactions · Contracts · Watchlist
Related docs
- Get an API key — create or rotate the key used by RPC, REST, SDK, CLI, and MCP
- Starkscan RPC — copy the mainnet node URL or use header-auth JSON-RPC
- Concepts — base URL, auth, tiers, cursors
- Reference — generated catalogs and download artifacts
- Launch matrix — RPC, REST, SDK, CLI, and MCP readiness
- Classify addresses in bulk — ordered summaries and address intelligence for wallet/paymaster backends
- Advanced utilities — batch previews and summaries
- Route certification — public states, evidence dimensions, and nightly proof
- Migration skills — source-backed
SKILL.mdartifacts for Voyager and accounting workflows - Self-serve account routes — session-authenticated key lifecycle and usage
- Monitor 10 wallets — one starter, three surfaces
- Agent HTTP quickstart — bounded setup for coding agents that call Starkscan directly
- Privacy Pool data API — public activity, note evidence, prepared metrics, and finalized public-flow boundaries
- Privacy Pool metrics and note evidence — copyable status, analytics, bucket, series, event, commitment, and nullifier integrations
OpenAPI tags (map only)
The sidebar under API reference is the full list. This table orients you:
| Tag | Covers |
|---|---|
| Account | Session-authenticated self-serve API-key lifecycle and usage |
| Addresses | Activity, txs, holdings, aggregate views |
| Blocks | Block metadata and lists |
| Contracts | Class, verification, reads |
| Reference | Generated OpenAPI artifacts and reference surfaces |
| Search | Explorer search |
| Status | Chain status |
| Tokens | Metadata, supply, balances, transfers |
| Transactions | Detail, previews, related reads |
| Utilities | Helpers; key tier may vary (Advanced utilities) |
Protocol-domain surfaces are part of the public contract only when Starkscan documents them explicitly.
Tiers
| Tier | Meaning |
|---|---|
| Official public API | Default contract in /docs and Scalar |
| Advanced utilities | Supported, often needs a broader key |
| Partner | Partner-only indexed analytics such as token-holder census |
Start on the official tier. Add utilities only for batch jobs that truly need them.
Certification states
The OpenAPI reference includes x-starkscan-certification on each operation.
| State | Use it how |
|---|---|
certified | Safe default for production client workflows. Current launch set: status, block read, timestamp-to-block, tx read, token total supply, and token balance-of. |
beta | Usable with named clients and explicit limits. Treat indexed lists, holder census, and protocol routes as beta until their reconciliation gates are complete. |
experimental | Partner preview only. Do not depend on schema stability. |
unsupported | Not for client use. |
Correctness is the hard launch gate. For token reads, use block_tag=<block_number> or block_tag=<block_hash> when you need reproducible exactness; latest and pending are live moving state. If a route is slow but certified, use backoff, caching, or a lower quota. If a route is fast but not yet reconciled, keep it out of unattended production paths.
Self-serve account routes
/v1/me/* is the personal-workspace control plane behind the signed-in /api-key experience.
- Auth with a Better Auth session, not
X-Starkscan-Api-Key. GET/HEAD/OPTIONSmay use the hosted browser session cookie or a bearer session token.POST/DELETEmust useAuthorization: Bearer <session_token>.GET /v1/me/api-keyslists metadata only.POST /v1/me/api-keysissues or rotates the default live read + batch + write key. Write execution remains separately gated; see Self-serve account routes.DELETE /v1/me/api-keys/{public_id}revokes one key.GET /v1/me/usagereturns recent request activity, failures, and per-key aggregates.
See Self-serve account routes for the exact auth shape and examples.
First calls
If you do not have a key yet, create one from the hosted API keys page. One server-side integration may use its scoped key across RPC, REST, the TypeScript SDK, the CLI, and MCP; browser-direct integrations need a dedicated client key and must never share it with a server-side service. Do not use the same-origin explorer /v1/* lane for external integrations. If your software asks for a Starknet node URL, start with Starkscan RPC.
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/status"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/token/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/total-supply?block_tag=latest"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/token/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/balance-of/0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8?block_tag=latest"For deterministic checks, resolve a block first and pass it as block_tag:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/token/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/balance-of/0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8?block_tag=10000000"Block reads
The safe block reads in the public spec come with concrete examples and defaults:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/blocks?limit=25"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/block/8717378?tx_limit=50"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/block/8717378/txs?limit=25"Wallet loop
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8/activity?limit=50"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8/transactions?limit=50"curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8/token-holdings"Filter transfers to several wallets—repeat address=:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/token/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/transfers?address=0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8&address=0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a&limit=100"One POST for many summaries (utility key tier):
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
-d '{"addresses":["0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a","0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8"]}' \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/summaries"Readable attribution for one address:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a/attribution"Batch deployment, attribution, and inbound-funds flags:
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
-d '{"addresses":["0x040337b1af3c663e86e333bab5a4b28da8d4652a15a69beee2b677776ffe812a","0x259fec57cd26d27385cd8948d3693bbf26bed68ad54d7bdd1fdb901774ff0e8"]}' \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/address/intelligence"Full scripted loop: Monitor 10 wallets.
Agent pitfalls
POST …/tx/previews→ body{"hashes":[...]}POST …/address/summaries→ body{"addresses":[...]}POST …/address/intelligence→ body{"addresses":[...]}GET …/address/{address}/attribution→ readable alias and indexed deployment/token metadata for one addressGET …/contract/.../read→ Starknet selector, not a Cairo nameGET …/verification404 → no record yet, not a missing route- 403 on a documented utility → key scope, not a typo in
X-Starkscan-Api-Key
Token reads
balance-of— you know the contract address.token-holdings— you care about the wallet; symbols like USDC can point at more than one live contract.- Big screens: avoid N×
balance-of; use holdings. - Gate on holdings only when
completeness.complete=true; legacy clients should also requireexact=true,truncated=false, andcompleteness.reasonCode="complete". GET /v1/{chain}/token/{token}/holdersis a partner-tier indexed holder census for token-contract-first analytics. It is not a substitute for transfer-history replay and is intentionally outside the default read-key lane.
Partner holder example:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/token/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/holders?limit=50"Cross-layer messages, events, and token facts
Beyond the core reads, these public (read-tier) routes are easy to miss — full request/response schemas are in the API reference:
Cross-layer messages (L1 ↔ L2)
GET /v1/{chain}/messages— paginated canonical cross-layer messagesGET /v1/{chain}/contract/{address}/messages— canonical messages for one contractGET /v1/{chain}/contract/{address}/bridge-signals— L2 bridge-signal activity for one contract
Indexed events
GET /v1/{chain}/events— paginated, API-decoded events with optional address, positionaltopic0..topic15, and block filtersGET /v1/{chain}/contract/{address}/events— contract-scoped event search with the same positionaltopic0..topic15contract
Copy-paste global event search:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/events?selector=0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9&address=0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d&from_block=10630000&to_block=10630325&limit=100"Copy-paste contract-scoped event search:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/contract/0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d/events?selector=0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9&topic1=0x5983efa05a23ecc4eb29d8717f86b34412964ea152d6a499ea447fcf5f5ee39&topic2=0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8&from_block=10630000&to_block=10630325&limit=100"Event filters are exact-position matches on both routes. topic0..topic15 map to key positions zero through fifteen; repeated values at one position are OR, populated positions are AND, and omitted positions are wildcards. selector aliases topic0. The contract route also keeps key/keys as sequential singleton compatibility inputs; the global route rejects those aliases because they do not identify a position. Each position is capped at 128 distinct felts, each request at 256 total, pages at 100. Any topic1..topic15 filter requires topic0 plus explicit numeric from_block and to_block bounds spanning at most 10,000 blocks inclusive. An address alone is not a topic0 anchor. Topic0-only searches may page the retained history; full-history later-position search is not yet provided.
Voyager /events migration
Voyager-style indexers usually call GET /events?contract={market}&p={page}&ps=100 and stop at lastPage. Starkscan uses the indexed contract-events route instead:
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/contract/$MARKET_ADDRESS/events?from_block=$FROM_BLOCK&to_block=$TO_BLOCK&limit=100"Keep calling the same route until nextCursor is null. Treat nextCursor as opaque and URL-encode it before putting it in the query string:
cursor="$(printf '%s' "$NEXT_CURSOR" | jq -sRr @uri)"
curl \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/contract/$MARKET_ADDRESS/events?from_block=$FROM_BLOCK&to_block=$TO_BLOCK&limit=100&cursor=$cursor"Response shape is { items, nextCursor, eventDecodingDegraded }, not { items, lastPage }.
Field mapping for Voyager adapters:
| Voyager field | Starkscan field |
|---|---|
items | items |
lastPage | stop when nextCursor is null |
keys | keys (the complete canonical key array; topic0..topic3 are compatibility aliases) |
name | eventName when attributed, otherwise null |
selector | topic0 |
transactionHash | txHash |
transactionNumber | txIndex |
number | logIndex |
timestamp | Unix seconds from timestampIso |
data | data |
Event decoding contract
Contract, global, block-detail, and transaction-detail event views use one server-certified contract. data[] is authoritative and keys[] is the canonical indexed key array; legacy rows without payload.keys reconstruct only topic0..topic3, so their keys[] may be incomplete. decodedFields is emitted only when an exact materialized ABI schema or a reviewed standard selector-and-arity schema consumes the full payload; its source/reason fields carry provenance.
decoded: typeddecodedFieldsare certified for the payload.name_only: the event name is attributed, but typed fields are not certified.unknown: no attribution is available at the event's execution class.
eventDecodingDegraded: true means only that the optional attribution lookup failed for this response, not that an individual event is unknown. Starkscan does not call RPC, Voyager, class-ABI, or trace sources at request time to fill event fields. The web UI consumes the API result directly.
Fully decoded response:
{"items":[{"blockNumber":10630025,"timestampIso":"2026-07-15T12:00:00Z","txHash":"0xabc","txIndex":4,"logIndex":1,"address":"0xcontract","keys":["0xtransfer","0xfrom","0xto"],"topic0":"0xtransfer","topic1":"0xfrom","topic2":"0xto","topic3":null,"data":["0x1","0x0"],"decodingStatus":"decoded","eventName":"Transfer","eventNameSource":"class_abi","decodedFields":[{"label":"amount","type":"core::integer::u256","source":"data","kind":"u256","status":"decoded","rawValues":["0x1","0x0"],"originIndexes":[0,1],"displayValue":"1","addressValue":null,"numericValue":"1","textValue":null,"boolValue":null}],"decodedFieldsSource":"class_abi"}],"nextCursor":null,"eventDecodingDegraded":false}Name-only response:
{"items":[{"blockNumber":10630024,"timestampIso":"2026-07-15T11:59:00Z","txHash":"0xdef","txIndex":3,"logIndex":0,"address":"0xcontract","keys":["0xapproval","0xowner"],"topic0":"0xapproval","topic1":"0xowner","topic2":null,"topic3":null,"data":["0x10","0x0"],"decodingStatus":"name_only","eventName":"Approval","eventNameSource":"selector_unique","decodedFieldsUnavailableReason":"selector_only_attribution"}],"nextCursor":null,"eventDecodingDegraded":false}Unknown response:
{"items":[{"blockNumber":10630023,"timestampIso":"2026-07-15T11:58:00Z","txHash":"0x123","txIndex":2,"logIndex":7,"address":"0xcontract","keys":["0xunmatched","0x7"],"topic0":"0xunmatched","topic1":"0x7","topic2":null,"topic3":null,"data":["0xbeef"],"decodingStatus":"unknown"}],"nextCursor":null,"eventDecodingDegraded":false}Token facts
GET /v1/{chain}/token/{token}/controls— indexed token control facts
GET /v1/{chain}/token/{token}/markets/pools (DEX pool facts) and GET /v1/{chain}/token/{token}/holders/analytics (holder-concentration analytics) also exist but require a partner-tier key — read-tier keys receive 403.
Privacy Pool public data
The beta external surface includes status, analytics, metric-buckets,
metrics/series, events, commitments, and nullifiers. See
Privacy Pool metrics and note evidence for
copyable requests, response gates, cursor handling, and intentional exclusions.
GET /v1/{chain}/privacy-pool/tvl is the current finalized snapshot.
GET /v1/{chain}/privacy-pool/tvl/series returns prepared hourly points, not
a certified continuous history. Both are beta and can be called directly over
REST or through the current Starkscan SDK.
Server-to-server clients normally omit Origin. Browser clients may call external api.starkscan.co/v1 or app-host /api/v1 REST routes from arbitrary origins by sending the API key in X-Starkscan-Api-Key or X-Api-Key; responses use wildcard, non-cookie CORS (Access-Control-Allow-Origin: * without Access-Control-Allow-Credentials). Both preflights and actual requests carrying bearer, cookie, internal, or legacy credentials are excluded from that wildcard lane and remain subject to Starkscan's strict origin allowlist. The public edge also strips caller-supplied internal keys as credential sanitization; stripping does not move those requests into the wildcard lane. MCP, OAuth, WSS, and self-serve session requests remain restricted to Starkscan-configured origins. Never embed a shared production key in a public frontend bundle; use a client-specific key with bounded scope and rate limits.
curl --fail-with-body --silent --show-error \
-D /tmp/starkscan-tvl.headers \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/privacy-pool/tvl" \
-o /tmp/starkscan-tvl.jsonKeep the returned weak ETag and revalidate it. If-None-Match takes precedence over If-Modified-Since; a matching request returns 304 with no body:
etag="$(awk 'BEGIN{IGNORECASE=1} /^etag:/ {sub(/^[^:]+:[[:space:]]*/, ""); sub(/\r$/, ""); print; exit}' /tmp/starkscan-tvl.headers)"
curl --fail-with-body --silent --show-error \
-H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
-H "If-None-Match: $etag" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/SN_MAIN/privacy-pool/tvl" \
-o /dev/null -w '%{http_code}\n'Raw decimal-string token amounts are authoritative.
coverage.latestEventCursor is the exact block:tx:log public-flow watermark
for current-snapshot same-block reconciliation. coverage.finalizedOnly=true means
starkscan_indexed_finalized_tier, not L1 settlement; call the snapshot
L1-accepted only when coverage.asOfL1Accepted=true. The L1 fields are null
when indexed acceptance evidence is unavailable. The request path never calls
RPC, another explorer, or a price provider. Schema-v1 valuation fields are
legacy compatibility fields, not a pricing contract for new integrations.
Hourly pages require inclusive, UTC-hour-aligned from and to RFC3339
timestamps plus granularity=hour. They return at most 24 oldest-first points;
pass nextCursor unchanged until it is null. An empty items[] page or
sparse UTC-hour sequence is unavailable history, not a zero-value point or
complete-range assertion; fail closed. The current page shape has no
page-level coverage or freshness watermark, so do not synthesize missing hours
from RPC, another explorer, or request-time pricing. Hourly assets include
nonzero public-flow amounts and amount-incomplete degraded entries; known zero
amounts are omitted until reactivated.
Keyed api.starkscan.co responses remain private. The trusted same-origin /v1 lane may use public caching but is reserved for Starkscan app traffic. ETag is authoritative. On the current snapshot, Last-Modified is the current process cache-generation time, so a cold process may conservatively return 200; on an hourly page, it is the newest materialization timestamp in that page.
Every request
- For data-plane routes under
/v1/{chain}/*, sendX-Starkscan-Api-Key; browser-direct REST clients may useX-Api-Key. - The explorer's same-origin
/v1lane is trusted and distinct from the external API-host/v1and app-host/api/v1lanes. - Never paste full API keys into chats, tickets, screenshots, or PR comments. Redact them like
mzk_REDACTED_KEY. - For workspace-control routes under
/v1/me/*, use Better Auth sessions for your personal Starkscan workspace: cookie auth for safe reads, bearer token required forPOST/DELETE. - Log
X-Request-Idwhen you open a support thread. - Expect client/server failures as JSON with
code,message,docSlug, andrequestId. TheX-Request-Idheader is canonical. - Log
X-Starkscan-Route-Classwhen present so agents can back off by class. - Treat
503as retryable when it indicates temporary unavailability, and honorRetry-Afterwhen present. - Sanity-check a host with
npx -y @starkscan/cli doctor(base URL, auth, reachability, hosted MCP), plus astatuscall and a deliberately bad key to confirm401.
503 example
HTTP/1.1 503 Service Unavailable
Retry-After: 5
X-Request-Id: mzk-...
Content-Type: application/json; charset=utf-8
{"code":"service_unavailable","message":"Temporarily unavailable","docSlug":"api/retry","requestId":"mzk-..."}Honor Retry-After. Do not spin in a tight loop.
401 / 403 examples
HTTP/1.1 401 Unauthorized
X-Request-Id: mzk-...
WWW-Authenticate: Bearer realm="starkscan", error="invalid_token"
Content-Type: application/json; charset=utf-8
{"code":"unauthorized","message":"Unauthorized","docSlug":"api/auth","requestId":"mzk-..."}HTTP/1.1 403 Forbidden
X-Request-Id: mzk-...
WWW-Authenticate: Bearer realm="starkscan", error="insufficient_scope", scope="batch"
Content-Type: application/json; charset=utf-8
{"code":"forbidden","message":"Forbidden","docSlug":"api/auth","requestId":"mzk-..."}Not REST?
| Need | Doc |
|---|---|
| Typed TS | SDK |
| Shell | CLI |
| MCP | MCP quickstart |
| Starknet JSON-RPC provider | Starkscan RPC |
| Click-only | Explorer |
Documented elsewhere on purpose
- MCP transport → MCP quickstart
- Batch address classification → Classify addresses in bulk
- Batch helpers → Advanced utilities
- Protocol domains → hidden until explicitly exposed
- Private ops → not part of your contract