REST API for Second Layer.
Base URL: https://api.secondlayer.tools
| Need | Product | Base path |
|---|---|---|
| Raw ordered chain events | Stacks Streams | /v1/streams |
| Decoded token and NFT transfer events | Stacks Index | /v1/index |
| App-specific materialized tables | Stacks Subgraphs | /v1/subgraphs (reads — anon for public subgraphs, wildcard CORS) · /api/subgraphs (management) |
Product endpoints require a Bearer token:
curl -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/v1/streams/tipGet a session token via the CLI: sl login
Read-only L1 event feed. Cursors use <block_height>:<event_index>.
GET /v1/streams/events # cursor-paginated raw events
GET /v1/streams/events/:tx_id # events emitted by one tx
GET /v1/streams/blocks/:heightOrHash/events # events in one block
GET /v1/streams/canonical/:height # canonical block hash lookup
GET /v1/streams/reorgs?since=...&limit=... # recorded reorg metadata
GET /v1/streams/tip # current tip and ingest lag
Every successful event response includes { events, tip, reorgs }; paginated
responses also include next_cursor.
L2 decoded events. v1 is intentionally limited to transfer endpoints.
GET /v1/index/ft-transfers
GET /v1/index/nft-transfers
Both endpoints support cursor, from_cursor, limit, from_height,
to_height, contract_id, sender, and recipient. NFT transfers also
support asset_identifier.
Responses use the same envelope pattern as Stacks Streams:
{ events, next_cursor, tip, reorgs }.
GET /v1/index/transactions/:tx_id/proof
Returns a trustless proof that a transaction is included in a Stacks (Nakamoto) block, and that the reward cycle's signers attested to that block. The proof is self-verifying: a consumer recomputes everything client-side and trusts nothing Second Layer returned. Open beta — no read auth.
curl https://api.secondlayer.tools/v1/index/transactions/0x.../proof200 response:
{
"txid": "<hex>",
"index_block_hash": "<hex>",
"block_height": 8199502,
"tx_index": 0,
"raw_tx": "<hex>",
"raw_header": "<hex>",
"tx_merkle_path": [{ "position": "left", "hash": "<hex>" }],
"consensus": {
"reward_cycle": 136,
"reward_set": {
"signers": [{ "signing_key": "<hex>", "weight": 51 }],
"total_weight": 3862
}
}
}consensus is present only when the reward set could be resolved; otherwise the
proof is anchored-only (still verifiable as included in a corroborable header,
without the signer-weight check).
Verify the proof client-side with verifyTransactionProof from
@secondlayer/sdk. For a fully-trustless consensus check,
resolve the reward set from your own stacks-node rather than trusting the
embedded set.
Proof errors:
| Status | Code | Description |
|---|---|---|
| 404 | PROOF_UNAVAILABLE |
Transaction or block not found |
| 503 | PROOF_TX_SET_INCOMPLETE |
Server couldn't reproduce the block's tx_merkle_root from its stored tx set, so it refuses to emit an unverifiable proof (fail-safe) |
Reads live on /v1/subgraphs — anon for public subgraphs (managed deploys
default public; BYO deploys default private), owner sk-sl_ bearer for private
(anon → 404). Wildcard CORS.
GET /v1/subgraphs # list public (+ caller's own with bearer)
GET /v1/subgraphs/:name # metadata + tables + tip
GET /v1/subgraphs/:name/:table # query rows
GET /v1/subgraphs/:name/:table/count
GET /v1/subgraphs/:name/:table/aggregate
GET /v1/subgraphs/:name/:table/:id
GET /v1/subgraphs/:name/:table/stream # SSE
GET /v1/subgraphs/:name/openapi.json # per-subgraph specs (anon for public)
GET /v1/subgraphs/:name/schema.json
GET /v1/subgraphs/:name/docs.md
Row routes return { rows, next_cursor, tip } with _id keyset pagination —
pass ?cursor=<next_cursor> to resume, _order=asc|desc for direction
(_offset/_sort rejected with 400). Visibility: deploy with
--visibility public|private, flip later with sl subgraphs publish|unpublish;
public names are a single global claim-on-publish namespace
(409 PUBLIC_NAME_TAKEN).
Management stays on /api/subgraphs (session or key):
GET /api/subgraphs # list
POST /api/subgraphs # deploy
GET /api/subgraphs/:name # get
DELETE /api/subgraphs/:name # delete
POST /api/subgraphs/:name/reindex # reindex
POST /api/subgraphs/:name/backfill # backfill a block range
POST /api/subgraphs/:name/stop # request operation cancellation
GET /api/subgraphs/:name/source # captured source for edit loops
GET /api/subgraphs/:name/gaps # gap inspection
GET /api/subgraphs/:name/openapi.json # generated OpenAPI 3.1 spec
GET /api/subgraphs/:name/schema.json # compact agent schema
GET /api/subgraphs/:name/docs.md # generated Markdown reference
GET /api/subgraphs/:name/:table # query table
GET /api/subgraphs/:name/:table/count
GET /api/subgraphs/:name/:table/:id
Table list routes return { data, meta }. Count routes return { count }.
The API can generate documentation from the deployed subgraph schema. These
routes use the same authentication and ownership checks as
GET /api/subgraphs/:name.
curl -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/api/subgraphs/token-transfers/openapi.json
curl -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/api/subgraphs/token-transfers/schema.json
curl -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/api/subgraphs/token-transfers/docs.mdFormats:
| Route | Format | Use |
|---|---|---|
/openapi.json |
OpenAPI 3.1 JSON | Docs systems, client generators, API catalogs |
/schema.json |
Compact JSON | Agent context with tables, columns, filters, endpoints, and static examples |
/docs.md |
Markdown | Human-readable API reference |
Pass ?server=<url> to override the server URL embedded in generated docs.
Signed HTTP webhooks. Polymorphic — a subscription is either subgraph (fires on a deployed subgraph table's rows) or chain (fires on raw chain events, no subgraph; forward-looking — starts at the chain tip, never backfills).
GET /api/subscriptions # list
POST /api/subscriptions # create
GET /api/subscriptions/:id # get
PATCH /api/subscriptions/:id # update
DELETE /api/subscriptions/:id # delete
POST /api/subscriptions/:id/pause # pause
POST /api/subscriptions/:id/resume # resume
POST /api/subscriptions/:id/rotate-secret # rotate signing secret
GET /api/subscriptions/:id/deliveries # recent delivery attempts
GET /api/subscriptions/:id/dead # dead-letter outbox rows
POST /api/subscriptions/:id/dead/:outboxId/requeue
POST /api/subscriptions/:id/replay # replay a block range
POST /api/subscriptions accepts a triggers array (1..50) for a chain
subscription, OR subgraphName + tableName for a subgraph subscription —
mutually exclusive.
curl -X POST -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/api/subscriptions \
-d '{
"name": "amm-swaps",
"url": "https://my-app.com/webhook",
"triggers": [
{ "type": "contract_call", "contractId": "SP....amm", "functionName": "swap-*" },
{ "type": "ft_transfer", "trait": "sip-010", "minAmount": "1000000" }
]
}'Trigger types and their fields (all string fields accept * wildcards; trait
scopes to contracts conforming to a SIP/trait; amounts are non-negative integer
strings or numbers):
type |
Fields |
|---|---|
contract_call |
contractId, functionName, caller, trait |
contract_deploy |
deployer, contractName |
ft_transfer / ft_mint / ft_burn |
assetIdentifier, sender, recipient, minAmount, trait |
nft_transfer / nft_mint / nft_burn |
assetIdentifier, sender, recipient, trait |
stx_transfer |
sender, recipient, minAmount, maxAmount |
stx_mint / stx_burn |
sender, recipient, minAmount |
stx_lock |
lockedAddress, minAmount |
print_event |
contractId, topic, trait |
Chain delivery envelope: each apply is chain.{type}.apply with body
{ action: "apply", block_hash, block_height, tx_id, canonical, trigger, event }.
On reorg you get chain.reorg.rollback with { action: "rollback", fork_point_height, orphaned: [{ tx_id, event }] }. Delivery is at-least-once: a
tx surviving a reorg re-delivers an apply under its new block_hash — key
consumer state on (tx_id, block_hash). Per-subscription HMAC signing (Standard
Webhooks) applies to both kinds.
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Invalid input |
| 401 | AUTHENTICATION_ERROR |
Missing or invalid token |
| 403 | AUTHORIZATION_ERROR |
Token valid but not authorized |
| 404 | SUBGRAPH_NOT_FOUND |
Resource not found |
| 429 | RATE_LIMIT_ERROR |
Rate limited |
| 500 | INTERNAL_ERROR |
Server error |