SDK
March 10, 2026
The Second Layer SDK is a typed TypeScript client for the Second Layer API. It handles authentication, request formatting, and response parsing so you can focus on building.
Install with bun add @secondlayer/sdk.
Getting started
Create a client instance with your API key. The default base URL points to the hosted API.
import { SecondLayer } from "@secondlayer/sdk"
const sl = new SecondLayer({
apiKey: "sk-sl_...",
baseUrl: "https://api.secondlayer.tools", // default
})
// Sub-clients are available as properties:
sl.streams // stream CRUD, deliveries, replay
sl.views // view deploy, query, reindexStreams
Create and manage webhook-based event streams. Streams push matching blockchain events to your endpoint in real time.
// Create — returns stream + webhook secret for HMAC verification
const { stream, webhookSecret } = await sl.streams.create({
name: "my-stream",
webhookUrl: "https://example.com/webhook",
filters: [
{ type: "stx_transfer", minAmount: 1_000_000 },
],
})
// List (optionally filter by status)
const { streams, total } = await sl.streams.list({ status: "active" })
// Get by ID (supports partial IDs)
const stream = await sl.streams.get("a1b2c3")
// Update filters or webhook URL
await sl.streams.update("a1b2c3", {
filters: [{ type: "contract_call", contractId: "SP1234...::token" }],
})
// Enable / disable / delete
await sl.streams.enable("a1b2c3")
await sl.streams.disable("a1b2c3")
await sl.streams.delete("a1b2c3")
// Bulk operations
await sl.streams.pauseAll()
await sl.streams.resumeAll()
// Rotate webhook secret
const { secret } = await sl.streams.rotateSecret("a1b2c3")
// Inspect deliveries
const { deliveries } = await sl.streams.listDeliveries("a1b2c3", {
limit: 20,
status: "failed",
})
const detail = await sl.streams.getDelivery("a1b2c3", "delivery-id")Views
Deploy, query, and manage indexed views. The query API supports filtering with comparison operators, sorting, pagination, field selection, and full-text search.
// List deployed views
const { data } = await sl.views.list()
// Get view details (tables, health, row counts)
const view = await sl.views.get("token-transfers")
// Query a table
const { data, meta } = await sl.views.queryTable(
"token-transfers",
"transfers",
{
sort: "_block_height",
order: "desc",
limit: 50,
filters: { sender: "SP1234...", "amount.gte": "1000000" },
fields: "sender,recipient,amount",
}
)
// meta = { total, limit, offset }
// Count rows
const { count } = await sl.views.queryTableCount(
"token-transfers",
"transfers",
{ filters: { sender: "SP1234..." } }
)
// Reindex from scratch
await sl.views.reindex("token-transfers", {
fromBlock: 150_000,
toBlock: 160_000,
})
// Delete view and all data
await sl.views.delete("token-transfers")Typed views
Import a view definition to get a fully typed query client. Table names, column names, and filter operators are all inferred from your schema.
import { getView } from "@secondlayer/sdk"
import myView from "./views/token-transfers"
// Standalone helper — accepts options, SecondLayer instance, or Views instance
const client = getView(myView, { apiKey: "sk-sl_..." })
const rows = await client.transfers.findMany({
where: { sender: { eq: "SP1234..." }, amount: { gte: 1000000n } },
orderBy: { _blockHeight: "desc" },
limit: 25,
})
const total = await client.transfers.count({
sender: { eq: "SP1234..." },
})
// Or via the SecondLayer instance
const typed = sl.views.typed(myView)
const rows = await typed.transfers.findMany({ ... })Error handling
All SDK methods throw ApiError on failure. The error includes the HTTP status code and a descriptive message.
import { ApiError } from "@secondlayer/sdk"
try {
await sl.streams.get("nonexistent")
} catch (err) {
if (err instanceof ApiError) {
err.status // 404
err.message // "Stream not found"
}
}
// Common status codes:
// 401 — API key invalid or expired
// 404 — Resource not found
// 429 — Rate limited (check Retry-After header)
// 5xx — Server error