Streams
Streams is the raw event firehose for Stacks. One service captures every event a Stacks node emits — STX, FT and NFT transfers, contract prints, locks — and saves them as an immutable, ordered log. You read that log over a cursor-paginated REST API.
Because the data is append-only and never changes, it's heavily cacheable and trivially replayable: sync, stop, and pick up exactly where you left off. The point is what you don't do — you never run a Stacks node. We shoulder data availability so you can build higher-level APIs and indexers on top. It's the same firehose every Foundation Dataset decoder reads internally. For push delivery, see Subscriptions.
How it works
so you never run a node
An indexer faces the Stacks node and writes raw, canonical events; the Streams API serves them to your consumer over a cursor. Stop and resume anytime — the cursor is just height:event_index.
Auth
Streams is read-only but keyed — every request needs an , including during open beta.
curl -H "Authorization: Bearer sk-sl_..." \
https://api.secondlayer.tools/v1/streams/tipReading the log
Every event carries a cursor — height:event_index. Page forward from a cursor and the stream is fully idempotent: persist the last cursor you saw and a restarted job resumes from exactly that point, no duplicates. Because the log is append-only it's also reorg-aware — when a fork resolves, /v1/streams/reorgs tells you which cursors to roll back, so your derived state stays consistent.
The SDK wraps this into a consume loop with checkpointing:
import { createStreamsClient } from "@secondlayer/sdk";
const streams = createStreamsClient({ apiKey: process.env.SL_API_KEY! });
await streams.events.consume({
fromCursor: lastCheckpoint,
types: ["print"],
contractId: "SP2QEZ06AGJ3RKJPBV14SY1V5BBFNAW33D96YPGZF.BNS-V2",
batchSize: 500,
onBatch: async (events, envelope) => {
for (const e of events) await handle(e);
await saveCheckpoint(envelope.next_cursor);
return envelope.next_cursor;
},
});Try it
Code · curl, fetch
curl "https://api.secondlayer.tools/v1/streams/events?limit=5" \
-H "Authorization: Bearer $SL_API_KEY"const res = await fetch(
"https://api.secondlayer.tools/v1/streams/events?limit=5",
{ headers: { Authorization: `Bearer ${process.env.SL_API_KEY}` } },
);
const data = await res.json();