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

# `sl.subgraphs`

Deploy a subgraph, read its rows and watch its operations. The same data the [Subgraphs API](/docs/api/subgraphs) serves over HTTP.

## Methods

### sl.subgraphs.list()

List the subgraphs deployed to this instance.

Each summary has the status, the last processed block against the chain tip, progress, `gapCount`, and an `integrity` flag.

REST twin: `GET /api/subgraphs`

```ts
sl.subgraphs.list(): Promise<{ data: SubgraphSummary[] }>
```

### sl.subgraphs.status(name)

Get a subgraph's health, sync position, and tables.

The verify step after a deploy, reindex, or backfill: poll it until
`sync` catches the tip.

REST twin: `GET /api/subgraphs/{name}`

```ts
sl.subgraphs.status(name: string): Promise<SubgraphDetail>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |

### sl.subgraphs.openapi(name, options)

Get a subgraph's OpenAPI spec.

Describes the REST routes generated for each table. Pass `serverUrl` to set the server the spec advertises.

REST twin: `GET /api/subgraphs/{name}/openapi.json`

```ts
sl.subgraphs.openapi(
  name: string,
  options?: SubgraphSpecOptions,
): Promise<Record<string, unknown>>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `options.serverUrl` | `string` | no |  |
| `options.generatedAt` | `string` | no |  |

### sl.subgraphs.schema(name, options)

Get a subgraph's schema as JSON, shaped for agents.

Per table: the endpoints, columns, indexes, which fields are sortable, selectable, searchable, or filterable, and a ready example query. Pass `serverUrl` to set the server the endpoints use.

REST twin: `GET /api/subgraphs/{name}/schema.json`

```ts
sl.subgraphs.schema(
  name: string,
  options?: SubgraphSpecOptions,
): Promise<SubgraphAgentSchema>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `options.serverUrl` | `string` | no |  |
| `options.generatedAt` | `string` | no |  |

### sl.subgraphs.markdown(name, options)

Get a subgraph's generated API docs as Markdown text.

Pass `serverUrl` to set the server the examples use.

REST twin: `GET /api/subgraphs/{name}/docs.md`

```ts
sl.subgraphs.markdown(name: string, options?: SubgraphSpecOptions): Promise<string>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `options.serverUrl` | `string` | no |  |
| `options.generatedAt` | `string` | no |  |

### sl.subgraphs.reindex(name)

Rebuild a subgraph from scratch.

A reindex always drops and rebuilds the whole subgraph, so it takes no
block range; the API rejects one with `REINDEX_RANGE_NOT_SUPPORTED`.
Use `backfill` to process a specific range. While a reindex or
backfill is already running the API answers 409 with code
`OPERATION_IN_PROGRESS`; poll `operations` until it finishes.

REST twin: `POST /api/subgraphs/{name}/reindex`

```ts
sl.subgraphs.reindex(name: string): Promise<ReindexResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |

### sl.subgraphs.stop(name)

Stop a running reindex or backfill.

Requests the cancel and resolves with the `operationId` and a `cancel_requested` status. Poll `getOperation` until `status` is terminal. Throws a 404 with code `NO_OPERATION` when nothing is running.

REST twin: `POST /api/subgraphs/{name}/stop`

```ts
sl.subgraphs.stop(
  name: string,
): Promise<{ message: string; operationId?: string; status?: string }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |

### sl.subgraphs.backfill(name, options)

Process one block range. 409 `OPERATION_IN_PROGRESS` while another
reindex or backfill runs on this subgraph.

REST twin: `POST /api/subgraphs/{name}/backfill`

```ts
sl.subgraphs.backfill(
  name: string,
  options: { fromBlock: number; toBlock: number },
): Promise<ReindexResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `options.fromBlock` | `number` | yes |  |
| `options.toBlock` | `number` | yes |  |

### sl.subgraphs.gaps(name, opts)

List the block ranges a subgraph is missing.

Each gap has its `start`, `end`, `size`, `reason`, and when it was detected and resolved. `resolved` filters to resolved or open gaps; page with `limit` and `offset`. `meta` reports the total gap count and total missing blocks.

REST twin: `GET /api/subgraphs/{name}/gaps`

```ts
sl.subgraphs.gaps(
  name: string,
  opts?: { limit?: number; offset?: number; resolved?: boolean },
): Promise<SubgraphGapsResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `opts.limit` | `number` | no |  |
| `opts.offset` | `number` | no |  |
| `opts.resolved` | `boolean` | no |  |

### sl.subgraphs.delete(name, options)

Delete a subgraph and its tables.

Permanent. The API cancels any running reindex or backfill first, waits up to 5 seconds for it to stop, then drops the subgraph's tables and registry entry. `force: true` is sent as `?force=true`.

REST twin: `DELETE /api/subgraphs/{name}`

```ts
sl.subgraphs.delete(
  name: string,
  options?: { force?: boolean },
): Promise<{ message: string }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `options.force` | `boolean` | no |  |

### sl.subgraphs.rows(name, table, params)

Read a table's rows, one cursor page at a time.

Anon works on an open instance; pass an apiKey on the client where reads
are closed. `next_cursor` is set
on every page that returned rows and is `null` only on an empty page, so a
poller tails with `cursor = page.next_cursor ?? cursor`. A paging loop
ends on an empty page (or one shorter than `limit`).

REST twin: `GET /v1/subgraphs/{name}/{table}`

```ts
sl.subgraphs.rows<T = unknown>(
  name: string,
  table: string,
  params?: {
    limit?: number;
    filters?: Record<string, string>;
    fields?: string;
    order?: string;
    sort?: string;
  }
  & { cursor?: string },
): Promise<SubgraphRowsEnvelope<T>>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `table` | `string` | yes |  |
| `params.limit` | `number` | no |  |
| `params.filters` | `Record<string, string>` | no |  |
| `params.fields` | `string` | no |  |
| `params.order` | `string` | no |  |
| `params.sort` | `string` | no |  |
| `params.cursor` | `string` | no |  |

### sl.subgraphs.operations(name)

Recent reindex/backfill operations for a subgraph, newest first.

REST twin: `GET /api/subgraphs/{name}/operations`

```ts
sl.subgraphs.operations(
  name: string,
): Promise<{ operations: SubgraphOperationStatus[] }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |

### sl.subgraphs.getOperation(name, operationId)

Get the status of one reindex, backfill, or stop operation.

Poll the `operationId` returned by `reindex`, `backfill`, or `stop`
until `status` is terminal.

REST twin: `GET /api/subgraphs/{name}/operations/{operationId}`

```ts
sl.subgraphs.getOperation(
  name: string,
  operationId: string,
): Promise<SubgraphOperationStatus>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `operationId` | `string` | yes |  |

### sl.subgraphs.deploy(data)

Create or update a subgraph. A deploy that needs a rebuild queues one;
if a reindex or backfill is already running the API answers 409 with
code `OPERATION_IN_PROGRESS` (an `ApiError`, not a dedicated class).

REST twin: `POST /api/subgraphs`

```ts
sl.subgraphs.deploy(data: DeploySubgraphRequest): Promise<DeploySubgraphResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `description` | `string` | no |  |
| `sources` | `Record<string, Record<string, unknown>>` | yes |  |
| `schema` | `Record<string, unknown>` | yes |  |
| `handlerCode` | `string` | yes |  |
| `startBlock` | `number` | no | Override the definition's startBlock for this deploy only. |
| `sourceCode` | `string` | no | Original TypeScript source, persisted so chat can read/diff/edit later. |
| `dryRun` | `boolean` | no | Validate the definition + print the DDL plan without deploying. |

### sl.subgraphs.getSource(name)

Get a deployed subgraph's source.

Returns `sourceCode`, the bundled `handlerCode` the runtime executes, and the `pin` hash of what you deployed. `handlerCode` and `pin` are `null` for local deploys. A subgraph deployed before source was captured returns `sourceCode: null` and `readOnly: true` with a `reason`; redeploy it to capture the source.

REST twin: `GET /api/subgraphs/{name}/source`

```ts
sl.subgraphs.getSource(name: string): Promise<SubgraphSource>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |

### sl.subgraphs.bundle(data)

Bundle a TypeScript subgraph source on the server. Used by the web chat
authoring loop so Vercel's serverless runtime doesn't have to run esbuild.

REST twin: `POST /api/subgraphs/bundle`

```ts
sl.subgraphs.bundle(data: { code: string }): Promise<BundleSubgraphResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | `string` | yes |  |

### sl.subgraphs.count(name, table, params)

Count the rows in a table that match a filter.

The count is exact. Same auth posture as `rows`.

REST twin: `GET /v1/subgraphs/{name}/{table}/count`

```ts
sl.subgraphs.count(
  name: string,
  table: string,
  params?: { filters?: Record<string, string> },
): Promise<{ count: number }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `table` | `string` | yes |  |
| `params.filters` | `Record<string, string>` | no |  |

### sl.subgraphs.aggregate(name, table, params)

Compute scalar aggregates over a table's matching rows.

Same auth posture as `rows`.

REST twin: `GET /v1/subgraphs/{name}/{table}/aggregate`

```ts
sl.subgraphs.aggregate(
  name: string,
  table: string,
  params?: SubgraphAggregateParams,
): Promise<SubgraphAggregateResponse>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |
| `table` | `string` | yes |  |
| `params.filters` | `Record<string, string>` | no |  |
| `params.count` | `boolean` | no |  |
| `params.countDistinct` | `string[]` | no |  |
| `params.sum` | `string[]` | no |  |
| `params.min` | `string[]` | no |  |
| `params.max` | `string[]` | no |  |

### sl.subgraphs.typed(def)

Returns a typed client for a subgraph defined with `defineSubgraph()`.
Row types are inferred from the subgraph's schema literal types.

```ts
sl.subgraphs.typed<T extends { name: string; schema: Record<string, unknown> }>(
  def: T,
): InferSubgraphClient<T>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `def` | `T` | yes |  |

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

const sbtcFlows = defineSubgraph({
	name: "sbtc-flows",
	startBlock: 8000000,
	sources: {
		transfers: {
			type: "ft_transfer",
			assetIdentifier:
				"SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token",
		},
	},
	schema: {
		transfers: {
			columns: {
				amount: { type: "uint" },
				sender: { type: "principal" },
				recipient: { type: "principal" },
			},
		},
	},
	handlers: {
		transfers: (event, ctx) => {
			ctx.insert("transfers", {
				amount: event.amount,
				sender: event.sender,
				recipient: event.recipient,
			});
		},
	},
});

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

const subgraph = sl.subgraphs.typed(sbtcFlows);

const page = await subgraph.transfers.findMany({
	where: { amount: { gte: 1_000_000n } },
	orderBy: { amount: "desc" },
	limit: 10,
	fields: ["sender", "recipient", "amount"],
});

for (const row of page.rows) {
	console.log(row.sender, row.recipient, row.amount);
}

console.log(page.nextCursor);
```

## Types

- `BundleSubgraphResponse`: A deployed subgraph's bundled sources, schema, and handler code.
  - `ok`: `true`
  - `name`: `string`
  - `description`: `string \| null`
  - `sources`: `Record<string, Record<string, unknown>>`
  - `schema`: `Record<string, unknown>`
  - `handlerCode`: `string`
  - `sourceCode`: `string`
  - `bundleSize`: `number`
- `DeploySubgraphRequest`: The body for deploying a subgraph: sources, schema, and handler code.
  - `name`: `string`
  - `description`?: `string`
  - `sources`: `Record<string, Record<string, unknown>>`
  - `schema`: `Record<string, unknown>`
  - `handlerCode`: `string`
  - `startBlock`?: `number`. Override the definition's startBlock for this deploy only.
  - `sourceCode`?: `string`. Original TypeScript source, persisted so chat can read/diff/edit later.
  - `dryRun`?: `boolean`. Validate the definition + print the DDL plan without deploying.
- `DeploySubgraphResponse`: What a deploy did, the version, and where indexing starts.
  - `action`: `"created" \| "unchanged" \| "handler_updated" \| "updated" \| "reindexed"`
  - `subgraphId`: `string`
  - `version`: `string`
  - `message`: `string`
  - `start_block`?: `number`. Effective indexing start height.
  - `operationId`?: `string`
  - `reindexStarted`?: `boolean`
  - `estimatedEvents`?: `number`. Bounded candidate-event denominator for the reindex just started, when the op was classifiable as sparse/light. Absent for heavy ops (no upfront estimate possible) — check `sl subgraphs status` after ~30s for a rate-based ETA instead.
  - `warnings`?: `string[]`. Non-blocking deploy lints (e.g. handler reads a print field never observed on-chain).
  - `pin`?: `string`. sha256 identity of what was deployed; absent on unbundled deploys.
  - `verification`?: `SubgraphVerification`
  - `diff`?: `{ addedTables: string[]; removedTables: string[]; addedColumns: Record<string, string[]>; breakingChanges: string[] }`
- `ReindexResponse`: A queued reindex and the block range it covers.
  - `message`: `string`
  - `fromBlock`: `number`
  - `toBlock`: `string \| number`
  - `operationId`?: `string`
  - `status`?: `"queued" \| "running" \| "cancel_requested"`
- `SubgraphAgentSchema`: A subgraph's tables and columns in a form agents can read.
  - `name`: `string`
  - `version`: `string`
  - `description`?: `string`
  - `schemaHash`?: `string`
  - `pin`?: `string`
  - `generatedAt`: `string`
  - `serverUrl`: `string`
  - `sources`?: `Record<string, unknown>`
  - `tables`: `Record<string, { endpoint: string; countEndpoint: string; aggregateEndpoint?: string; streamEndpoint?: string; rowCount: number; columns: Record<string, { type: string; nullable?: boolean; indexed?: boolean; searchable?: boolean; default?: string \| number \| boolean }>; indexes?: readonly readonly string[][]; uniqueKeys?: readonly readonly string[][]; query: { parameters: string[]; sortable: string[]; selectable: string[]; searchable: string[]; filters: string[] }; examples: { list: Record<string, unknown>; count: { count: number }; curl: string } }>`
- `SubgraphAggregateParams`: Request shape for `GET /v1/subgraphs/:subgraphName/:tableName/aggregate`.
  - `filters`?: `Record<string, string>`
  - `count`?: `boolean`
  - `countDistinct`?: `string[]`
  - `sum`?: `string[]`
  - `min`?: `string[]`
  - `max`?: `string[]`
- `SubgraphAggregateResponse`: Aggregate response.
  - `count`?: `number`
  - `countDistinct`?: `Record<string, number>`
  - `sum`?: `Record<string, string>`
  - `min`?: `Record<string, string \| null>`
  - `max`?: `Record<string, string \| null>`
- `SubgraphDetail`: A subgraph's version, pin, status, tables, and sources.
  - `name`: `string`
  - `version`: `string`
  - `schemaHash`?: `string`
  - `pin`?: `string \| null`. sha256 over schema, handler bundle, startBlock, network and runtime. Null for subgraphs deployed before pins existed.
  - `verification`?: `SubgraphVerification \| null`. Null for subgraphs deployed before levels were derived.
  - `status`: `string`
  - `lastProcessedBlock`: `number`
  - `description`?: `string`
  - `sources`?: `Record<string, unknown>`
  - `definition`?: `Record<string, unknown>`
  - `health`: `{ totalProcessed: number; totalErrors: number; errorRate: number; lastError: string \| null; lastErrorAt: string \| null; emptyMapping: boolean }`
  - `sync`: `SubgraphSyncInfo`
  - `tables`: `Record<string, { endpoint: string; columns: Record<string, { type: string; nullable?: boolean; indexed?: boolean; searchable?: boolean; default?: string \| number \| boolean }>; rowCount: number; example: string; indexes?: readonly readonly string[][]; uniqueKeys?: readonly readonly string[][] }>`
  - `createdAt`: `string`
  - `updatedAt`: `string`
- `SubgraphGapEntry`
  - `start`: `number`
  - `end`: `number`
  - `size`: `number`
  - `reason`: `string`
  - `detectedAt`: `string`
  - `resolvedAt`: `string \| null`
- `SubgraphGapRange`
  - `start`: `number`
  - `end`: `number`
  - `size`: `number`
  - `reason`: `string`
- `SubgraphGapsResponse`: Missing block ranges in a subgraph, with totals.
  - `data`: `SubgraphGapEntry[]`
  - `meta`: `{ total: number; totalMissingBlocks: number; limit: number; offset: number }`
- `SubgraphOperationStatus`: Status of a tracked reindex/backfill operation (poll until terminal).
  - `id`: `string`
  - `subgraphName`: `string`
  - `kind`: `"reindex" \| "backfill"`
  - `status`: `"queued" \| "running" \| "completed" \| "failed" \| "cancelled"`
  - `fromBlock`: `number \| null`
  - `toBlock`: `number \| null`
  - `processedBlocks`: `number \| null`
  - `progress`: `number \| null`. 0–1 fraction; null when no denominator is known yet. 1 when completed.
  - `error`: `string \| null`
  - `startedAt`: `string \| null`
  - `finishedAt`: `string \| null`
  - `createdAt`: `string`
  - `updatedAt`: `string`
- `SubgraphRowsEnvelope`: /v1 cursor envelope for subgraph table reads.
  - `rows`: `T[]`
  - `next_cursor`: `string \| null`. Set on every page that returned rows; `null` only on an empty page.
  - `tip`: `{ block_height: number; subgraph_height: number; blocks_behind: number }`
- `SubgraphSource`: A deployed subgraph's source code, handler bundle, and pin.
  - `name`: `string`
  - `version`: `string`
  - `sourceCode`: `string \| null`
  - `handlerCode`?: `string \| null`. The bundled handler the runtime executes; null for local deploys.
  - `pin`?: `string \| null`. sha256 of `pinPreimage`; null for local deploys.
  - `pinPreimage`?: `string \| null`. The canonical JSON the pin hashes (handler hash, network, runtime, schema hash, startBlock); null before it was stored.
  - `readOnly`: `boolean`
  - `reason`?: `string`
  - `updatedAt`: `string`
- `SubgraphSpecOptions`: Options for generating a subgraph's agent schema.
  - `serverUrl`?: `string`
  - `generatedAt`?: `string`
- `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`
- `SubgraphSyncInfo`
  - `status`: `"synced" \| "catching_up" \| "reindexing" \| "error"`
  - `mode`?: `"sync" \| "reindex"`
  - `startBlock`: `number`
  - `lastProcessedBlock`: `number`
  - `chainTip`: `number`. Backward-compatible denominator for progress displays. During reindexing, this is the reindex target block rather than the live source chain tip.
  - `sourceChainTip`?: `number`
  - `targetBlock`?: `number`
  - `blocksRemaining`: `number`
  - `processedBlocks`?: `number`
  - `totalBlocks`?: `number`
  - `progress`: `number`
  - `queue`?: `{ position: number \| null; estimatedEvents: number \| null; estimatedStartSeconds: number \| null }`. Present while the populating operation is queued: approximate claim position + honest event denominator + naive start estimate.
  - `estimatedEvents`?: `number`. Event-based progress for sparse syncs (block pct is meaningless when most heights are skipped).
  - `processedEvents`?: `number`
  - `etaSeconds`?: `number \| null`
  - `gaps`: `{ count: number; totalMissingBlocks: number; ranges: SubgraphGapRange[] }`
  - `integrity`: `"complete" \| "gaps_detected" \| "history_filling"`. history_filling = expected gaps while a tip-first backfill op runs.
- `SubgraphVerification`: How far a subgraph's rows can be checked without trusting the server, derived from its sources and a scan of its handler.
  - `level`: `"events" \| "state" \| "none"`
  - `reasons`: `string[]`. What stands between the subgraph and checkable rows, e.g. `print_event source "swaps" needs event proofs` or `subgraph.ts:41:12 Date: wall-clock time differs per run`.
  - `unproven`: `string[]`. What stays unproven even at this level, e.g. tx attribution of writes.

## Notes for agents

- Methods live on 'sl.subgraphs' of 'const sl = new SecondLayer()'. Signatures and overloads are generated from the SDK source.
- 'typed' returns a client for a subgraph defined with defineSubgraph(), with its tables as typed members.
- A method's REST twin is linked on its entry; the Subgraphs API page serves the same data over HTTP.

## 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`
