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, reindex

Streams

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

Props

Constructor
apiKeystring
baseUrlstring'https://api.secondlayer.tools'
sl.streams
create(data){stream, webhookSecret}
list(params?){streams, total}
get(id)StreamResponse
update(id, data)StreamResponse
delete(id)void
enable(id) / disable(id)StreamResponse
rotateSecret(id){secret}
pauseAll() / resumeAll()BulkResponse
listDeliveries(id, params?){deliveries}
sl.views
list(){data: ViewSummary[]}
get(name)ViewDetail
deploy(data){action, viewId, message}
queryTable(name, table, params?){data, meta}
queryTableCount(name, table, params?){count}
reindex(name, options?){message, fromBlock, toBlock}
delete(name)void
typed(def)InferViewClient
Exports
@secondlayer/sdkSecondLayer, Streams, Views, getView, ApiError
@secondlayer/sdk/streamsStreams
@secondlayer/sdk/viewsViews, getView