Skip to content
USDC Price:$1.0000Gas:
Arcscan

REST API

Arcscan reads Arc through an HTTP API, and that API is reachable from outside with no key and no account — every path on this page was called over the public internet before it was written down.

Base URL#

The API has hostnames of its own, one per chain: api.arc-scan.io for Arc mainnet, chain 5042, and api-testnet.arc-scan.io for Arc Testnet, chain 5042002. These are the addresses to build against:

https://api.arc-scan.io/v1          # Arc mainnet, chain 5042
https://api-testnet.arc-scan.io/v1  # Arc Testnet, chain 5042002

Each host serves the same two surfaces: /v1/…, the typed REST API — one path per resource, returning the documents this explorer itself renders from — and /api?module=…&action=…, the explorer-compatible contract that existing tooling already speaks. The two hosts differ only in the chain they answer for, so moving a client to testnet is a change of host and nothing else.

The site’s own /_api path is not the API

You may notice that the explorer fetches /_api/v1/… from its own origin, and that the path answers if you call it yourself. It is the website’s internal rewrite to this same service, kept for the site’s own pages: it sends no CORS headers, it is served with a noindex header, and it can be changed for a reason that has nothing to do with you. Build against the hostnames above, and pin the host in one constant so you can change it in one place.

Callable from a browser — on these hosts only

The API hosts answer cross-origin: Access-Control-Allow-Origin: *, with Content-Type, X-Api-Key and If-None-Match allowed on a preflight, and ETag, X-Request-Id and the rate-limit headers exposed to the caller. The explorer’s own /_api path carries no Access-Control- header at all, so a fetch() aimed there fails in a browser while the same call succeeds in curl — which is exactly how the wrong address goes unnoticed.

Two surfaces#

The same node and the same index answer through two different shapes. The typed API is what this explorer renders from, so anything visible on a page can be read as JSON in the same form. The compatible API reproduces the de-facto explorer REST contract that wallets, deploy scripts and indexers already speak, so existing tooling can be pointed at Arc by changing one base URL.

SurfaceShapeSuccess envelope
/v1/…Typed REST, one path per resourceThe document itself, snake_case keys
/api?module=…&action=…One path, dispatched on query string{"status":"1","message":"OK","result":…}

Endpoints#

Every path below was called against mainnet when this page was written. All of them answered; two answered 501, and both are named where they belong rather than left out — a documented refusal is worth more than a gap.

Chain, home and head#

/v1/chain is the first call to make: it states the chain id, the native currency and its decimals, which capabilities this deployment has, and exactly how much of the chain is indexed. Read it rather than hard-coding any of that — the two chains differ, and Data coverage explains what the index block tells you.

curl https://api.arc-scan.io/v1/chain

# {"chain_id":5042,"name":"Arc","is_testnet":false,
#  "native":{"symbol":"USDC","decimals":18},
#  "block_time_ms":506,"finality":"instant",
#  "capabilities":{"trace":true,"archive":true,"debug":false,…},
#  "index":{"available":true,"complete":true,…}}
PathWhat it answers
GET /v1/chainChain constants, capabilities and index coverage
GET /v1/homeLatest blocks and transactions in one document
GET /v1/stats/summaryThe headline counters the home page shows
GET /v1/stats/gasFee tracker: current and recent gas prices
GET /v1/stream/headServer-sent events, one frame per new block

/v1/stream/head is an SSE stream, not a JSON document — it stays open and pushes a frame roughly twice a second. Every frame carries server_now so a client can compute an honest age without trusting its own clock.

curl -N https://api.arc-scan.io/v1/stream/head

# event: head
# data: {"height": 14852424, "hash": "0x68ce06…f3775", "timestamp": 1786360005,
#        "tx_count": 0, "server_now": 1786360007, "available": true}

Blocks#

A block reference is a height, a hash, or the literal latest. Lists take limit and an opaque cursor; see Blocks for what the fields mean.

curl "https://api.arc-scan.io/v1/blocks?limit=2"
curl  https://api.arc-scan.io/v1/blocks/latest
curl  https://api.arc-scan.io/v1/blocks/14852000
curl "https://api.arc-scan.io/v1/blocks/14852000/txs?limit=10"

Transactions#

A transaction is addressed by hash. The call tree is available on mainnet because trace is true there; the state diff is not, because debug is false, and it says so with a 501 rather than an empty answer.

H=0x128cb07da24289341bcc57e3129ef78f82b5a1769c30156774487fe9e98251e9

curl "https://api.arc-scan.io/v1/txs?limit=5"
curl "https://api.arc-scan.io/v1/txs/$H"
curl "https://api.arc-scan.io/v1/txs/$H/raw"
curl "https://api.arc-scan.io/v1/txs/$H/trace"

curl "https://api.arc-scan.io/v1/txs/$H/state"
# 501 {"error":{"code":"CAPABILITY_UNAVAILABLE",…}}  — no debug namespace on mainnet

Addresses and tokens#

A=0xf40dc55b06e4e8c042430ecf1995d0ec6b9ae033
T=0x3600000000000000000000000000000000000000   # the native currency as an ERC-20

curl "https://api.arc-scan.io/v1/address/$A"
curl "https://api.arc-scan.io/v1/address/$A/txs?limit=10"
curl "https://api.arc-scan.io/v1/address/$A/activity?limit=10"
curl "https://api.arc-scan.io/v1/address/$A/logs?limit=10"
curl "https://api.arc-scan.io/v1/address/$A/tokens"

curl "https://api.arc-scan.io/v1/tokens/$T"
curl "https://api.arc-scan.io/v1/tokens/$T/info"

Holders are indexed on mainnet, so /v1/tokens/{address}/holders answers for ordinary tokens. It refuses for the native currency at 0x3600…0000, and the message explains why: those balances are account balances, and indexing them twice would double-count every account.

Search, charts and decode#

Search resolves what the explorer’s own box resolves — a height, a hash, an address. The charts index lists every metric with its id, and /v1/charts/{metric} returns that one series; tx is one of them. Decode is the only POST on this page and needs no chain lookup at all.

curl "https://api.arc-scan.io/v1/search?q=0x3600000000000000000000000000000000000000"
curl "https://api.arc-scan.io/v1/search/suggest?q=0x36"
curl  https://api.arc-scan.io/v1/charts
curl  https://api.arc-scan.io/v1/charts/tx

curl -X POST https://api.arc-scan.io/v1/decode \
  -H 'content-type: application/json' \
  -d '{"input":"0xa9059cbb…"}'

# {"selector":"0xa9059cbb","decoded":{"name":"transfer",
#  "signature":"transfer(address,uint256)","args":[…]},"error":null}

The compatible surface#

/api?module=…&action=… speaks the familiar explorer contract: one path, a module and an action, and every scalar in result as a string. The proxy module passes JSON-RPC reads through and answers in JSON-RPC shape rather than the status envelope.

curl "https://api.arc-scan.io/api?module=proxy&action=eth_blockNumber"
# {"jsonrpc":"2.0","id":1,"result":"0xe2a12f"}

curl "https://api.arc-scan.io/api?module=account&action=balance&address=0xf40dc55b06e4e8c042430ecf1995d0ec6b9ae033"
# {"status":"1","message":"OK","result":"20456053552414099311"}

curl "https://api.arc-scan.io/api?module=stats&action=ethsupply"

Not every action of that contract exists here — the ones that need data Arc does not have, or an index we do not keep, are refused by name. The on-site API referenceOpens in a new tab lists the full dispatch table and which actions are unavailable; it is generated from the service itself, so it cannot drift the way a page like this one can.

Reading the JSON#

Four conventions cause every integration bug worth naming. The first is the expensive one: an 18-decimal amount does not survive a JavaScript Number, so amounts cross the wire as strings and must be parsed as such.

ConventionWhat it means on the wire
AmountsAn object, not a number: raw (integer string), decimals, formatted (exact), usd, symbol. Gas figures and supplies are decimal strings too. Block heights are JSON numbers.
CasingKeys are snake_case. Addresses and hashes are lowercase, with the display form beside them in a checksum field. Any input case is accepted.
TimeUnix seconds, never pre-formatted. Blocks arrive about twice a second and timestamps have one-second resolution, so they are not a total order — never sort or paginate by them.
PaginationOpaque cursors under page.next, not page numbers. page.has_more tells you whether to keep going.

Committed history is immutable, and cached like it

A response keyed by a committed height or a mined hash is served Cache-Control: public, max-age=31536000, immutable. Arc has instant finality and no reorganisations, so that is a real guarantee rather than a probabilistic one: cache those responses forever and skip the request entirely.

Errors#

A refusal is a JSON object with a machine-readable code, a message written for a human, and an optional detail. Branch on code, never on the message text.

curl https://api.arc-scan.io/v1/blocks/999999999999
# 404
# {"error":{"code":"NOT_FOUND","message":"No block at height 999999999999","detail":null}}

curl https://api.arc-scan.io/v1/tokens/0x3600000000000000000000000000000000000000/holders
# 501
# {"error":{"code":"CAPABILITY_UNAVAILABLE",
#   "message":"The native gas token has no separate holder list: its balances are account
#              balances, and indexing them again would double-count every account.",
#   "detail":{"capability":"holder_index","detail_key":"holdersNativeToken"}}}
StatuscodeWhen
404NOT_FOUNDNo such block, transaction, address record or token.
429RATE_LIMITEDToo many requests. Carries Retry-After in seconds.
501CAPABILITY_UNAVAILABLEThe data needs a capability this chain or this deployment does not have.
502UPSTREAM_ERRORThe node answered, but not usefully.
503INDEX_OVERLOADEDA heavy query was shed rather than queued.
504UPSTREAM_TIMEOUTThe node did not answer in time.

Read the error envelope, not just the status

A malformed or out-of-range parameter — an unparseable block reference, a limit above the maximum, a missing required query, a mistyped JSON field — is answered with 400 {"error":{"code":"INVALID_INPUT","message":"limit: Input should be greater than or equal to 1","detail":null}}. The code classifies the mistake and the message names the field that caused it, which is usually faster than re-reading the URL. A 404 here means the resource genuinely does not exist.

Limits and access#

There is no API key, no tier and nothing to sign up for. Requests are metered per caller by a token bucket, and every response tells you where you stand — read the headers instead of hard-coding a number, because the budget is an operational setting and not a published promise.

HeaderMeaning
X-RateLimit-LimitThe size of the bucket the request was metered against.
X-RateLimit-RemainingTokens left in it.
Retry-AfterOn a 429 or a 503 only: whole seconds to wait before retrying.
X-Request-IdPresent on API responses. Quote it if you need to ask about a request.

Two other ceilings are real and are reported rather than hidden. Log queries are clamped to a maximum block span, and an over-range request is refused with that maximum named in the message instead of being silently truncated. Trace and state-diff calls run on a small dedicated pool with a wall-clock budget and a size cap, and set truncated: true rather than hanging.

Reading Arc without our index

If what you want is the chain rather than our view of it, we also run a public read-only JSON-RPC endpoint for mainnet — see Public RPC. There is also a plain-text /llms.txtOpens in a new tab describing what this explorer holds, for anything that reads prose before it reads a schema.
REST API · Arcscan docs | Arcscan