REST API
Every /v1 surface answers with the same envelope, cursor, and filter grammar, so one client shape reads Index, Streams, and subgraphs.
- Served at
SECONDLAYER_API_URL, with yourSECONDLAYER_API_KEYas a bearer token (Keys and billing). - On your own box: Self-host.
Where the detail lives:
- Per-endpoint parameters and response codes: API reference.
- Print-event schemas:
get-print-schema. - Proof routes (
/v1/proofs/*,/v1/index/transactions/:tx_id/proof): Verify.
- The cursor is
<block_height>:<event_index>. tip.finalized_heightis the burn-confirmation finality boundary. Rows at or below it never reorg, which is what afinalizedOnlyconsumer gates on.
Branch on code, not on the message.
| Field | Is |
|---|---|
request_id | Matches the X-Request-Id response header (sent on every response); quote it in any report |
feedback.url | Where to report a gap |
Report a problem (hosted)
Every error body on api.secondlayer.tools carries a request_id. When a call fails and you think the API is wrong or missing something, send that id back with one line on what you were trying to do:
The server attaches its own record of the failed call (method, path, status, code; kept 24 hours), so you don't resend it. Optional fields:
| Field | Holds |
|---|---|
kind_hint | missing_capability, bug, schema_gap, docs_mismatch, or perf |
expected | What you wanted back |
evidence | tx_id, block_height, contract_id, event_type, subgraph, table |
An Idempotency-Key header makes retries safe.
| Surface | Page size | Ordering |
|---|---|---|
| Subgraph tables | _limit (default 50, max 1000) | _id by default, or _sort=<column>&_order=asc|desc for one column |
| Index | limit (default 200, max 1000) | Oldest first; order=desc for newest first on events and ft/nft transfers |
| Streams | limit (default 100, max 1000) | Fixed by the keyset cursor, no order param |
Pass the previous response's next_cursor back as cursor, on Index and subgraph routes. Subgraph routes, sorting and search: Read rows.
On /v1 subgraph tables these return 400, not a clamp:
- A
_limitthat is a non-integer,0, negative, or over 1000. - An
_orderother thanascordesc. - A bare
limit,order, orsort.
Index also takes:
from_cursor: exclusive start, not withcursor.from_height,to_height.
Subgraph tables filter on any column, validated against the table's schema. Index and streams take a fixed set per endpoint; the Streams filter table lists the ones on /v1/streams/events.
Unknown params are rejected
Any unrecognized query param returns 400; the error suggests the closest valid param.
Streaming (SSE)
Two endpoints push rows as Server-Sent Events (Content-Type: text/event-stream) over one long-lived connection, each with a ping keepalive every 20s.
GET /v1/streams/events/stream: raw chain event firehose. Same filters as/v1/streams/events, plus a start position (cursor/from_cursor/from_height); with none it live-tails from the reorg-clamped tip.GET /v1/subgraphs/:name/:table/stream: new subgraph-table rows. Takes?since=<block_height>(replay from a height, then tail) plus the table's column filters.
Ergonomic clients: Streams, Read rows.
Aggregates
GET /v1/subgraphs/:name/:table/aggregate runs scalar aggregates over the same filtered set as the list and count endpoints.
| Param | Effect |
|---|---|
_count=true | COUNT(*) |
_countDistinct=col1,col2 | Distinct count per column |
_sum=col, _min=col, _max=col | Comma-separated; numeric columns only (uint/int, plus the system _block_height) |
Result types:
countandcountDistinct.colare JSON numbers.sum,minandmaxare lossless strings.- Over an empty set
sumis"0", andminandmaxarenull. - No aggregate param →
{ "count": <n> }.
| Status / code | Cause |
|---|---|
400 NON_NUMERIC_COLUMN | A non-numeric _sum/_min/_max column |
400 INVALID_COLUMN | An unknown column |
400 TOO_MANY_AGGREGATES | Past 32 aggregate columns per request |
Typed as sl.subgraphs.typed(def).<table>.aggregate(spec), or untyped as sl.subgraphs.aggregate(name, table, params): signatures in the SDK reference.
Batch reads
POST /v1/batch runs up to 10 /v1 reads in one round trip, for agents and dashboards that pay per request in latency.
Results come back in order as { "results": [{ "path", "status", "body" }] }.
- Each item runs through the same auth and limits as a direct call, with your
Authorizationheader forwarded. - Only paths under
/v1/index/,/v1/subgraphs,/v1/streams/and/v1/contractsare allowed. Anything else is a per-item400.