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

# `sl.archive`

Load a signed archive manifest, verify your data against it, and price and fetch what you are missing.

## Methods

### sl.archive.latest(against, opts)

Load the latest signed archive manifest.

Reads `<archive>/latest.json` unless `against` names another manifest URL. The manifest's ed25519 signature is checked against the archive signing key, and a missing or invalid one throws `ArchiveSignatureError`. `insecure: true` skips the check; `signature.verified` on the result says whether it held.

```ts
sl.archive.latest(against?: string, opts?: ArchiveLoadOptions): Promise<LoadedArchive>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `against` | `string` | no |  |
| `opts.insecure` | `boolean` | no |  |

### sl.archive.load(against, opts)

Load an archive manifest from a URL.

Takes http(s) URLs only; a local path throws a `ValidationError` and stays on the CLI. The signature check is the same as `latest`.

```ts
sl.archive.load(against: string, opts?: ArchiveLoadOptions): Promise<LoadedArchive>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `against` | `string` | yes |  |
| `opts.insecure` | `boolean` | no |  |

### sl.archive.status()

Get the archive's current operational state.

Reads the public `status.json`: `fresh`, `lagging`, `stale`, `gap`, `failed-audit`, or `source-unavailable`, plus how far the last signed publish reaches.

```ts
sl.archive.status(): Promise<ArchiveStatus>
```

### sl.archive.partitions(ref, filter)

List a loaded archive's partitions, optionally filtered.

Runs locally on the manifest you already loaded and makes no request. Filter by `dataset` (`blocks`, `transactions`, `events`, or `state_writes` when the archive publishes it) and a block range.

```ts
sl.archive.partitions(
  ref: LoadedArchive,
  filter?: { dataset?: ArchiveDataset; fromBlock?: number; toBlock?: number },
): ArchivePartition[]
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ref.manifest` | `ArchiveManifest` | yes |  |
| `ref.origin` | `string` | yes |  |
| `ref.root` | `string` | yes |  |
| `ref.isRemote` | `boolean` | yes |  |
| `ref.signature` | `{ verified: boolean; reason?: string }` | yes |  |
| `filter.dataset` | `ArchiveDataset` | no |  |
| `filter.fromBlock` | `number` | no |  |
| `filter.toBlock` | `number` | no |  |

### sl.archive.quote(input)

Price a fetch against your balance before you pay for it.

Nothing is charged. `flow` is `bootstrap` or `repair`. The quote has the bundle count, the price in USD, the free repair allowance applied, your balance, and `sufficient`. Needs an account key (`sk-sl_*`), not an instance token.

```ts
sl.archive.quote(input: { paths: string[]; flow: ArchiveFlow }): Promise<ArchiveQuote>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `paths` | `string[]` | yes |  |
| `flow` | `ArchiveFlow` | yes |  |

### sl.archive.fetch(input)

Get presigned download URLs for archive partitions.

Draws credits per partition bundle; your first six repair bundles each month are free. Lists longer than 64 paths go out in batches of 64. Each URL is short-lived (`expiresAt`); pass it to `download`. An empty balance throws `InsufficientArchiveCreditsError` with the shortfall. Needs an account key (`sk-sl_*`).

```ts
sl.archive.fetch(
  input: { paths: string[]; flow: ArchiveFlow },
): Promise<ArchiveFetchResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `paths` | `string[]` | yes |  |
| `flow` | `ArchiveFlow` | yes |  |

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

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

const manifest = await sl.archive.latest();
const wanted = sl.archive.partitions(manifest, {
	dataset: "blocks",
	fromBlock: 0,
	toBlock: 99_999,
});
const paths = wanted.map((p) => p.path);

const quote = await sl.archive.quote({ paths, flow: "bootstrap" });
if (!quote.sufficient) {
	console.error(`need $${quote.usd}, balance is short`);
	process.exit(2);
}

const { urls } = await sl.archive.fetch({ paths, flow: "bootstrap" });

for (const partition of wanted) {
	const url = urls.find((u) => u.path === partition.path)?.url;
	const bytes = await sl.archive.download(partition, { url });
	await Bun.write(`archive/${partition.path}`, bytes);
}
```

### sl.archive.download(partition, opts)

Download a partition's bytes and verify them.

The sha256 is checked against the manifest, and a mismatch throws `ArchiveSignatureError`. On the official archive host you must pass the presigned `url` from `fetch`, or it throws a `ValidationError`; any other host downloads straight from its archive tree.

```ts
sl.archive.download(
  partition: ArchivePartition,
  opts?: { url?: string },
): Promise<Uint8Array>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `partition.dataset` | `string` | yes |  |
| `partition.from_block` | `number` | yes |  |
| `partition.to_block` | `number` | yes |  |
| `partition.path` | `string` | yes |  |
| `partition.row_count` | `number` | yes |  |
| `partition.byte_size` | `number` | yes |  |
| `partition.sha256` | `string` | yes |  |
| `opts.url` | `string` | no |  |

### sl.archive.credits.balance()

Get your archive credit balance.

Returns `creditsUsdMicros` (millionths of a dollar, as a string) and your auto-refill setting. Needs an account key (`sk-sl_*`).

```ts
sl.archive.credits.balance(): Promise<ArchiveCreditsBalance>
```

### sl.archive.credits.checkout(input)

Start a card checkout for a credit pack.

`pack` is the dollar amount: 10, 25, 50, or 100. Resolves to the checkout `url`. Needs no account key.

```ts
sl.archive.credits.checkout(
  input: { email: string; pack: 10 | 25 | 50 | 100 },
): Promise<{ url: string }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | `string` | yes |  |
| `pack` | `10 \| 25 \| 50 \| 100` | yes |  |

### sl.archive.credits.refill(input)

Set or turn off automatic credit refill.

Pass `belowUsd` and `packUsd` to buy a pack whenever the balance drops below the threshold, or `{ off: true }` to stop. Needs an account key (`sk-sl_*`).

```ts
sl.archive.credits.refill(
  input: { belowUsd: number; packUsd: 10 | 25 | 50 | 100 } | { off: true },
): Promise<{ belowUsd: number | null; packUsd: number | null }>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | `{ belowUsd: number; packUsd: 10 \| 25 \| 50 \| 100 } \| { off: true }` | yes |  |

### sl.archive.verify(input)

Check your instance's data against a signed archive.

Runs `POST /v1/archive/verify` on your instance: read-only, and it compares raw identity digests only (the deep semantic check is CLI-only). Resolves to `clean`, `diverged`, or `unanchored`; a failed signature is `unanchored`, never `clean`. Narrow the check with `fromBlock` and `toBlock`. Verification is free.

```ts
sl.archive.verify(input: ArchiveVerifyInput): Promise<ArchiveVerifyResult>
```

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `against` | `string` | yes |  |
| `target` | `string` | no |  |
| `fromBlock` | `number` | no |  |
| `toBlock` | `number` | no |  |
| `insecure` | `boolean` | no |  |
| `publicKeyPem` | `string` | no |  |

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

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

const result = await sl.archive.verify({
	against: "https://archive.secondlayer.tools/latest.json",
	fromBlock: 0,
	toBlock: 99_999,
});

for (const range of result.ranges) {
	if (range.status === "match") continue;
	console.log(
		`${range.dataset} ${range.from_block}-${range.to_block}: ${range.status}`,
	);
}

if (result.status === "unanchored") process.exit(2);
if (result.status === "diverged") process.exit(1);
console.log("local data matches the archive");
```

## Types

- `ArchiveCreditsBalance`: Your archive credit balance and its refill settings.
  - `creditsUsdMicros`: `string`
  - `refill`: `{ belowUsd: number \| null; packUsd: number \| null; lastAt: string \| null }`
- `ArchiveDataset`: One archive dataset: blocks, transactions, events or state writes.
- `ArchiveFetchItem`
  - `path`: `string`
  - `url`: `string`
  - `expiresAt`: `string`
  - `chargedUsdMicros`: `number`
- `ArchiveFetchResult`: Download links for archive partitions and what they cost.
  - `urls`: `ArchiveFetchItem[]`
  - `chargedTotalUsdMicros`: `number`
  - `balanceAfterUsdMicros`: `number`
- `ArchiveFlow`: Whether an archive purchase is a bootstrap or a repair.
- `ArchiveLoadOptions`: Options for loading an archive manifest.
  - `insecure`?: `boolean`
- `ArchiveManifest`
  - `network`?: `string`
  - `coverage`?: `{ from_block: number; to_block: number }`
  - `partition_size_blocks`?: `number`
  - `range_digests`?: `RangeDigest[]`
  - `partitions`?: `ArchivePartition[]`
  - `signature`?: `string`
  - `key_id`?: `string`
- `ArchivePartition`: One archive file: dataset, block range, path, rows, bytes.
  - `dataset`: `string`
  - `from_block`: `number`
  - `to_block`: `number`
  - `path`: `string`
  - `row_count`: `number`
  - `byte_size`: `number`
  - `sha256`: `string`
- `ArchiveQuote`: The price of an archive request, after any free allowance.
  - `partitions`: `number`
  - `bundles`: `number`
  - `usdMicros`: `number`
  - `usd`: `string`
  - `freeAllowanceAppliedMicros`: `number`
  - `allowanceRemainingBundles`: `number`
  - `balanceUsdMicros`: `number`
  - `sufficient`: `boolean`
- `ArchiveState`: `"fresh" \| "lagging" \| "stale" \| "gap" \| "failed-audit" \| "source-unavailable"`
- `ArchiveStatus`: The archive's state, coverage, and a plain-language reason.
  - `schema_version`: `1`
  - `network`: `string`
  - `state`: `ArchiveState`
  - `detail`: `string`. Human-facing one-liner explaining the state, including WHY.
  - `generated_at`: `string`
  - `archive`: `{ snapshot_digest: string \| null; coverage_to_block: number \| null; promoted_at: string \| null; signing_key_id: string \| null }`
  - `source`: `{ tip_height: number \| null; finalized_height: number \| null; decoder_head: number \| null }`
  - `lag`: `{ blocks_behind_finalized: number \| null; blocks_behind_tip: number \| null; seconds_since_promotion: number \| null }`
  - `audit`: `{ complete: boolean; checked_at: string; } \| null`
  - `signature`?: `string`
  - `key_id`?: `string`
- `ArchiveVerifyInput`: What to verify against, and the optional block range.
  - `against`: `string`
  - `target`?: `string`
  - `fromBlock`?: `number`
  - `toBlock`?: `number`
  - `insecure`?: `boolean`
  - `publicKeyPem`?: `string`
- `ArchiveVerifyRangeStatus`: `"match" \| "digest-mismatch" \| "count-mismatch" \| "missing"`
- `ArchiveVerifyResult`: Whether the archive is clean, diverged, or unanchored, by range.
  - `status`: `"clean" \| "diverged" \| "unanchored"`
  - `target`: `string`
  - `against`: `string`
  - `signature`: `{ verified: boolean; reason?: string }`
  - `coverage`?: `{ from_block: number; to_block: number }`
  - `ranges`: `{ dataset: string; from_block: number; to_block: number; status: ArchiveVerifyRangeStatus; expected_digest: string \| null; actual_digest: string \| null }[]`
  - `reason`?: `string`
- `LoadedArchive`: A loaded archive manifest with its origin and signature check.
  - `manifest`: `ArchiveManifest`
  - `origin`: `string`
  - `root`: `string`
  - `isRemote`: `boolean`
  - `signature`: `{ verified: boolean; reason?: string }`
- `RangeDigest`
  - `dataset`: `RangeDigestDataset`
  - `from_block`: `number`
  - `to_block`: `number`
  - `row_count`: `number`
  - `digest`: `string \| null`. null when the range holds no rows — an empty range has no digest, and that is materially different from "a digest that happens to be of zero rows", which would compare equal across datasets.
  - `digest_spec`: `"md5:sql-identity-v1"`
- `RangeDigestDataset`: `"events" \| "blocks" \| "transactions" \| "state_writes"`

## Notes for agents

- Methods live on 'sl.archive' of 'const sl = new SecondLayer()'. Signatures and overloads are generated from the SDK source.
- Price a fetch with quote before fetch; fetch and credits need a hosted account key.

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