<!-- source: https://secondlayer.tools/docs/sdk-reference/client -->

# Client

What the client does itself, beside its surfaces: batched reads and the one-call account snapshot. Constructor options are in the [SDK guide](/docs/sdk).

## Methods

### new SecondLayer(options)

```ts
new SecondLayer(
  options?: {
    baseUrl?: string;
    apiKey?: string;
    accountKey?: string;
    fetchImpl?: FetchLike;
    dumpsBaseUrl?: string;
    archiveBaseUrl?: string;
    archiveOpsUrl?: string;
    origin?: "cli" | "mcp" | "session";
    verify?: boolean | { publicKey: string; };
    verifyDumpsManifest?: boolean;
    requestTimeoutMs?: number;
    etagCache?: false | EtagCache;
  },
)
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `baseUrl` | `string` | no | Base URL of the instance API (trailing slashes are stripped). |
| `apiKey` | `string` | no | Bearer token for authenticated requests. Defaults to `SECONDLAYER_API_KEY` on `api.secondlayer.tools`, `INSTANCE_TOKEN` elsewhere. |
| `accountKey` | `string` | no | Hosted account key (`sk-sl_*`). Env `SECONDLAYER_API_KEY`. Used by `sl.archive` quote/fetch/credits; reaches `/v1` and `/api` only through `apiKey` when the base URL is the hosted API. |
| `fetchImpl` | `FetchLike` | no | Fetch implementation. Tests and edge runtimes can provide their own. |
| `dumpsBaseUrl` | `string` | no | Public base URL for Streams bulk parquet dumps (the cold backfill plane). Required for `streams.dumps.*`; without it the dumps client falls back to its built-in default. |
| `archiveBaseUrl` | `string` | no | Public base URL for the signed canonical archive tree. Defaults to `https://archive.secondlayer.tools`. Reaches `sl.archive`. |
| `archiveOpsUrl` | `string` | no | Credits and fetch-gate API. Defaults to `https://api.secondlayer.tools`, not `baseUrl`. Reaches `sl.archive` quote/fetch/credits. |
| `origin` | `"cli" \| "mcp" \| "session"` | no | Deploy origin label sent as `x-sl-origin` (telemetry). Defaults to `cli`. |
| `verify` | `boolean \| { publicKey: string; }` | no | Check the ed25519 signature on every Streams read. Omit for lenient (verify when signed, pass through unsigned self-host responses), `true` or `{ publicKey }` for strict, `false` for off. Reaches `sl.streams`. |
| `verifyDumpsManifest` | `boolean` | no | Check the dumps manifest signature before trusting any file hash (default on). Reaches `sl.streams.dumps`. |
| `requestTimeoutMs` | `number` | no | How long one request may take, headers and body, before it fails with a retryable `ApiError` (default 30 000 ms). A hung socket then trips the retry policy instead of stalling a walk or consume loop forever. `0` disables the timeout. |
| `etagCache` | `false \| EtagCache` | no | Where GET responses carrying an `ETag` are kept, so a repeat read sends `If-None-Match` and a finalized page comes back as a free `304`. Defaults to an in-memory store of the last 64 pages. Pass your own to keep it across processes (a file, a KV), or `false` to turn it off. |

### sl.batch(requests)

Run up to 10 public reads in one round trip (`POST /v1/batch`).

Each item is authorized on its own; the client's bearer token applies to
every item. Results come back in order as `{ path, status, body }`. Only
paths under `/v1/index/`, `/v1/subgraphs`, `/v1/streams/`, and
`/v1/contracts` are allowed; anything else is a per-item 400.

REST twin: `POST /v1/batch`

```ts
sl.batch(
  requests: { path: string; params?: Record<string, string | number | boolean> }[],
): Promise<{ results: { path: string | null; status: number; body: unknown }[] }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `requests` | `{ path: string; params?: Record<string, string \| number \| boolean> }[]` | yes |  |

```ts
import { SecondLayer } from "@secondlayer/sdk";

const sl = new SecondLayer({
	apiKey: process.env.SECONDLAYER_API_KEY,
});

const { results } = await sl.batch([
	{ path: "/v1/streams/tip" },
	{ path: "/v1/index/pox5/events", params: { topic: "stake", limit: 5 } },
]);

for (const result of results) {
	console.log(result.path, result.status);
}
```

### sl.context()

Get a one-call snapshot of your account and instance.

Returns a [`ContextSnapshot`](/docs/sdk-reference/client#t-ContextSnapshot): the same orientation an MCP agent reads
from `secondlayer://context`, available to any SDK or CLI consumer. Reads
run concurrently; a failed read lands as `{ value: null, error }` on its
field rather than rejecting the whole snapshot.

REST twin: `GET /api/accounts/me`

```ts
sl.context(): Promise<ContextSnapshot>
```

## Types

- `ActiveSubgraphOperation`
  - `subgraph`: `string`
  - `operationId`: `string`
  - `kind`: `"reindex" \| "backfill"`
  - `status`: `"queued" \| "running" \| "completed" \| "failed" \| "cancelled"`
  - `progress`: `number \| null`
- `ContextAccount`
  - `email`: `string`
- `ContextField`: One snapshot field: the value, or `null` plus the error that produced it.
  - `value`: `T \| null`
  - `error`?: `ContextFieldError`
- `ContextFieldError`: Why one snapshot field could not be read.
  - `message`: `string`
  - `code`?: `string`. Stable code from the API envelope (`UNAUTHORIZED`, `INDEX_NOT_READY`) or the SDK (`REQUEST_TIMEOUT`); absent for a bare transport failure.
  - `status`?: `number`. HTTP status; `0` when the API could not be reached.
  - `retryable`: `boolean`. Whether the same read can succeed on a retry (429, 5xx, network).
- `ContextSnapshot`: A point-in-time orientation snapshot: live tips and what this instance holds.
  - `account`: `ContextField<ContextAccount>`
  - `streamsTip`: `ContextField<StreamsTip>`
  - `indexTip`: `ContextField<IndexTip>`
  - `subgraphs`: `ContextField<SubgraphSummary[]>`
  - `webhooks`: `ContextField<{ count: number; byStatus: Record<string, number> }>`
  - `activeOperations`: `ContextField<ActiveSubgraphOperation[]>`
  - `instance`: `ContextField<InstanceDiagnosis>`
- `EtagCache`: Storage for `etagCache`.
  - `get`: `(key: string) => EtagEntry \| Promise<EtagEntry \| undefined> \| undefined`
  - `set`: `(key: string, entry: EtagEntry) => Promise<void> \| void`
- `EtagEntry`: One cached GET response: its validator and the body it validated.
  - `etag`: `string`
  - `body`: `string`
- `FetchLike`: `(input: string \| URL \| Request, init?: RequestInit) => Promise<Response>`
- `IndexTip`
  - `block_height`: `number`
  - `finalized_height`: `number`. Highest height treated as immutable (past the burn-confirmation finality boundary). Rows at or below it never reorg — `finalizedOnly` consumers gate on this, since Index rows carry no per-event flag.
  - `lag_seconds`: `number`
- `InstanceDiagnosis`: An instance's state, the API verdict, and the issues found.
  - `state`: `InstanceState`
  - `overall`: `string`. The API's own top-level verdict, passed through unmodified.
  - `issues`: `InstanceIssue[]`
- `InstanceIssue`
  - `title`: `string`. What is wrong, in one line.
  - `detail`?: `string`. Why — assembled only from what the payload actually reports.
  - `nextSteps`: `string[]`. What to run next, most useful first.
- `InstanceState`: `"healthy" \| "empty-index" \| "degraded"`
- `StreamsTip`: The chain tip: latest block, Bitcoin anchor, and finality boundary.
  - `block_height`: `number`
  - `block_hash`: `string`
  - `burn_block_height`: `number`
  - `finalized_height`?: `number`. Highest Stacks block past the burn-confirmation finality boundary. Optional for back-compat; the API always sets it.
  - `lag_seconds`: `number`
  - `oldest_seekable_height`?: `number \| null`. Oldest height still seekable on the live API for the caller's tier (`tip - retention`). `null` = unlimited retention. Older reads must use the cold dumps lane. Optional for back-compat.
  - `oldest_cursor`?: `string \| null`. Oldest seekable cursor (`<oldest_seekable_height>:0`); `null` = unlimited.
- `SubgraphSummary`: A subgraph's name, status, and indexing progress.
  - `name`: `string`
  - `version`: `string`
  - `status`: `string`
  - `lastProcessedBlock`: `number`
  - `totalProcessed`: `number`
  - `totalRows`?: `number`
  - `totalErrors`: `number`
  - `tables`: `string[]`
  - `chainTip`: `number`
  - `sourceChainTip`?: `number`
  - `targetBlock`?: `number`
  - `progress`: `number`
  - `blocksRemaining`?: `number`
  - `syncMode`?: `"sync" \| "reindex"`
  - `gapCount`: `number`
  - `integrity`: `"complete" \| "gaps_detected" \| "history_filling"`. history_filling = expected gaps while a tip-first backfill op runs.
  - `lastError`?: `string \| null`. Most recent indexing error reason + when it occurred, if any.
  - `lastErrorAt`?: `string \| null`
  - `updatedAt`?: `string \| null`. Last row mutation timestamp; powers per-card freshness.
  - `createdAt`: `string`

## Notes for agents

- These methods are on 'sl' itself ('sl.batch', 'sl.context'), not on a surface.
- 'sl.batch' sends up to 10 public reads as one POST /v1/batch; its REST twin is linked on the entry.

## Setup

Ensure setup once, skipping any step already done:
- Skill: `bunx skills add ryanwaits/secondlayer --skill secondlayer -y`
- CLI: `bun add -g @secondlayer/cli`
- Instance: `secondlayer init` writes `INSTANCE_TOKEN`. Loopback `/v1` reads need no key.
- Hosted/archive: export `SECONDLAYER_API_KEY` (`sk-sl_*`) or buy credits with `secondlayer credits buy` (`balance`, `refill`), then `secondlayer whoami`
