> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heretic.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Collector reference

> Install @heretic-hq/collector, call its browser API, configure endpoint resolution and probes, and understand its result.

`@heretic-hq/collector` opens an edge session, runs browser measurements, and posts the payload. It returns a request ID for a later server-side read.

## Install

```bash theme={null}
npm install @heretic-hq/collector
```

The package supports named and default imports:

```js theme={null}
import { heretic } from '@heretic-hq/collector';
```

```js theme={null}
import heretic from '@heretic-hq/collector';
```

It also exports `resolveEndpoint(config?)`, `PROBE_VERSION`, `SURFACE_VERSION`, and `RENDER_VERSION`.

There is no public `Collector` class, `createCollector`, `collect` method, or `heretic.explain` method.

## Run one collection

```js theme={null}
import { heretic } from '@heretic-hq/collector';

const result = await heretic({
  siteKey: 'hrtc_live_0123456789abcdef01234567',
});

if (result.ok && result.requestId) {
  console.log(result.requestId);
} else {
  console.warn(result.reason);
}
```

The return type is:

```ts theme={null}
Promise<{
  ok: boolean;
  requestId?: string | null;
  reason?: string;
}>
```

The function converts collection failures into `{ ok: false, reason }`. It starts one run per page. Later calls reuse the first run and do not apply a new configuration.

`requestId` is a join key, not a verdict. Ordinary success is `{ ok: true, requestId: "<32 lowercase hex>" }`. The edge returns it only after the tenant projection is committed. ZDR success is exactly `{ ok: true }` and has no request ID.

## Public site key

`siteKey` accepts a public key in this form:

```text theme={null}
hrtc_live_<24 lowercase hex>
```

The key attributes a settled projection to a site. It is safe in browser code. It does not sign collection and cannot authorize verdict reads.

The type makes `siteKey` optional, but a missing, unknown, or revoked value does not produce a tenant-readable attribution. Use an `hrtc_sk_...` secret only on the server.

## Configuration

| Option          | Type                    | Default              | Effect                                                                               |
| --------------- | ----------------------- | -------------------- | ------------------------------------------------------------------------------------ |
| `siteKey`       | `string`                | none                 | Public site attribution key                                                          |
| `endpoint`      | `string`                | resolved             | Selects a POP and skips endpoint resolution; known POP names map to their TCP origin |
| `quicEndpoint`  | `string`                | paired automatically | Sets the paired QUIC origin for a custom deployment                                  |
| `controlPlane`  | `string`                | unset                | Enables control-plane endpoint discovery; also requires `siteKey`                    |
| `token`         | `string`                | unset                | Sends a bearer token only to configured control-plane discovery                      |
| `tag`           | `string`                | unset                | Adds free text to `GET /n`                                                           |
| `doh`           | `string` or `DohConfig` | unset                | Enables TXT-record endpoint discovery                                                |
| `auto`          | `boolean`               | `true`               | Waits for page load and an idle opportunity before starting                          |
| `idleTimeoutMs` | `number`                | `2000`               | Limits the idle wait                                                                 |
| `postTimeoutMs` | `number`                | `8000`               | Limits the final payload POST                                                        |
| `quicWindowMs`  | `number`                | `1500`               | Sets the target window for the follow-up HTTP/3 attempt                              |
| `surface`       | `boolean`               | `true`               | Collects the declared-surface section                                                |
| `render`        | `boolean`               | `true`               | Collects fixed canvas and WebGL rendering output                                     |
| `zdr`           | `boolean`               | `false`              | Posts to `/z` and skips normal durable settlement writes                             |
| `budgetMs`      | `number`                | `8000`               | Sets the section budget for the core measured phase                                  |
| `rawSurface`    | `boolean`               | `false`              | Includes exact forms for a narrow set of surface values                              |
| `fetchImpl`     | `typeof fetch`          | global `fetch`       | Supplies fetch to endpoint discovery helpers                                         |
| `fallbackEdges` | `string[]`              | built-in list        | Replaces the fallback array; the resolver uses its first entry                       |

The DoH object has this shape:

```ts theme={null}
type DohConfig = {
  name: string;
  providers?: Array<{
    url: string;
    mode: 'wire' | 'json';
  }>;
};
```

## Endpoint resolution

`resolveEndpoint(config?)` checks candidates in this order:

1. `config.endpoint`
2. An HTTPS endpoint in `globalThis.__hereticEdge`
3. A non-expired endpoint in `sessionStorage`
4. DoH discovery when `doh` is set
5. Control-plane discovery when `controlPlane` is set
6. The first configured fallback, or `https://tcp.waw.probe.heretic.tech`

```js theme={null}
import { resolveEndpoint } from '@heretic-hq/collector';

const endpoint = await resolveEndpoint({
  siteKey: 'hrtc_live_0123456789abcdef01234567',
});
```

The built-in fallback contains Warsaw. You can pin a deployed edge when needed:

| Location  | Endpoint                             |
| --------- | ------------------------------------ |
| Warsaw    | `https://tcp.waw.probe.heretic.tech` |
| Singapore | `https://tcp.sin.probe.heretic.tech` |
| San Jose  | `https://tcp.sjc.probe.heretic.tech` |

The browser must connect directly to the selected measurement edge. A proxy in front of that endpoint changes the connection being measured.

## Request sequence

A normal run makes these requests:

```text theme={null}
GET  tcp.<pop>/n[?tag=...]
GET  quic.<pop>/healthz?n=<nonce>
GET  quic.<pop>/healthz?n=<nonce>&join=1
POST tcp.<pop>/c?n=<nonce>
```

With `zdr: true`, the final request is:

```text theme={null}
POST /z?n=<nonce>
```

The first health response teaches a fresh QUIC origin its Alt-Svc route. The second health request carries the nonce over HTTP/3 when the browser supports it. The TCP origin does not advertise HTTP/3.

## Script tag

The browser build defines a global `heretic` function:

```html theme={null}
<script src="/heretic.js"></script>
<script>
  heretic({ siteKey: 'hrtc_live_0123456789abcdef01234567' });
</script>
```

You can serve `dist/heretic.js` from your origin. An edge-hosted bundle self-targets the edge that served it. Call the global with a public key for tenant attribution:

```html theme={null}
<script src="https://probe.heretic.tech/c.js"></script>
<script>
  heretic({ siteKey: 'hrtc_live_0123456789abcdef01234567' });
</script>
```

## Collection controls

### `surface`

`surface: false` removes the declared-surface section. Current rules read fields from that section, so disabling it can reduce coverage or remove findings. The payload records the configured omission.

### `render`

`render: false` omits fixed canvas and WebGL raster collection. It does not disable identity. The base measured payload can still supply canvas or audio anchors, and the machine ID uses cores, display, and timezone.

### `rawSurface`

`rawSurface: false` keeps the surface section but coarsens or omits exact forms of:

* WebRTC host candidate addresses
* battery level and finite charge or discharge times
* storage quota and usage

The payload can still contain address counts and families, a coarse battery level, and storage buckets.

### `budgetMs`

The collector checks `budgetMs` between sections in the core measured phase. A section already running may finish after the budget. Skipped sections record `skipped: 'budget-cut'` and appear in `budget.cut`.

Fixed rendering and declared-surface collection run afterward, outside `budgetMs`. Declared-surface sections have their own deadlines. `budgetMs` is not a whole-run deadline.

### `zdr`

`zdr: true` posts to `/z`. Parsed claims stay request-local. A valid request creates no assessment, identity, projection, NDJSON record, request ID, or retrievable session. It removes the owned per-nonce session and linked state, then returns `{ ok: true }`.

Malformed or incomplete ZDR submissions retain only a hidden, nonpersistable baseline marker. They retain none of the submitted request data.

## Worker CSP

The parallel probe uses `blob:` workers. If the page's CSP blocks them, the payload records:

```text theme={null}
parallel.failure: "csp-blocked"
```

Other collection continues. Allow the worker with:

```text theme={null}
worker-src blob:
```

## Payload versions

| Payload                                     | Version |
| ------------------------------------------- | ------: |
| Top-level measured payload, `PROBE_VERSION` |     `3` |
| Nested `compute.version`                    |     `2` |
| Declared surface, `SURFACE_VERSION`         |     `3` |
| Fixed rendering, `RENDER_VERSION`           |     `1` |

## Continue on the server

Pass the returned request ID unchanged to your server. See [Tenant verdict endpoint](/api/verdict-endpoint).


## Related topics

- [Heretic documentation](/index.md)
- [Quickstart](/quickstart.md)
- [Signal reference](/reference/signals.md)
- [Tenant verdict schema](/api/verdict-schema.md)
- [Troubleshooting](/troubleshooting.md)
