# Collector API

The collector exports three functions. With npm, import them from `@octetproof/collector`. With the script tag, they are properties of the global `octet`.

```js
import { start, ready, verify } from '@octetproof/collector';
```

## `start(config)`

```ts
start(config: CollectorConfig): void
```

Begins a collection in the background and returns at once. Call it at page load. Calling `start()` again replaces the configuration, discards any stored result, and begins a new collection.

Throws at once if `config.apiUrl` is not a valid `https://` URL, or if `config.mode` is not `'full'`, `'lite'` or `'passive'`. See [`CollectorConfig`](#collectorconfig).

## `ready()`

```ts
ready(): Promise<ReadyResult>
```

Resolves once your edge has passed the latest collection to Octet, which means your backend can fetch the verdict. Call it at the moment of action.

- If the collection that `start()` began is still running, `ready()` waits for it.
- If that collection finished less than 90 seconds ago, `ready()` resolves at once with its result.
- Otherwise, `ready()` runs a new collection and waits for it.

Rejects if `start()` was never called, if the POST to your edge fails, or if the collection was aborted. A failed collection is not stored, so the next `ready()` call runs a new one.

## `verify(config)`

```ts
verify(config: CollectorConfig): Promise<EdgeReply>
```

Runs one collection and resolves with your edge's reply. It stores nothing, so each call collects again. Use it where there is no separate moment of action. It rejects at once for an invalid `apiUrl` or `mode`, and later if the POST to your edge fails or the collection is aborted.

## `CollectorConfig`

| Field | Type | Required | Description |
|---|---|---|---|
| `apiUrl` | `string` | Yes | Your edge's origin, for example `https://octet.example.com`. Must be `https://`. `http://` is accepted only for `localhost`, `127.0.0.1` and `[::1]`. |
| `sessionRef` | `string` | Yes | The unguessable reference your backend created for this page view. Your backend fetches the verdict with it. |
| `mode` | `'full' \| 'lite' \| 'passive'` | No | How much network measurement to do. The default is `'full'`. See [Modes](#modes). |
| `wsUrl` | `string` | No | The WebSocket URL for your edge. The default is `apiUrl` with `https` replaced by `wss`, plus `/v1/ws`. Set it only if your edge's WebSocket has a different address. |
| `signal` | `AbortSignal` | No | Cancels the collection. See [Cancelling](#cancelling). |

## Modes

| Mode | What it does | Network requests beyond your edge |
|---|---|---|
| `full` | Everything, for the strongest results | HTTPS and UDP 3478 to three Octet network hosts |
| `lite` | Skips the HTTPS timing requests | UDP 3478 to three Octet network hosts |
| `passive` | No network measurement and no WebRTC | None |

No mode shows a permission prompt. The hosts are listed in [Network and CSP](/docs/browser/reference/network/).

## `ReadyResult`

| Field | Type | Description |
|---|---|---|
| `response` | `EdgeReply` | Your edge's reply to the POST. |
| `issuedAtMs` | `number` | When the edge replied, in milliseconds since the Unix epoch. |
| `expiresAtMs` | `number` | When `ready()` stops reusing this result, in milliseconds since the Unix epoch. `issuedAtMs` plus 90 seconds. |
| `elapsedMs` | `number` | Milliseconds from the start of collection to the edge's reply. |

## `EdgeReply`

In production your edge always replies with:

```json
{ "ok": true }
```

The reply never contains the verdict. Your backend fetches it. See [Fetch the Verdict](/docs/browser/integration/fetch-verdict/). Only the testing setting `EDGE_EXPOSE_VERDICT` changes this reply. See [Edge Configuration](/docs/browser/reference/edge-config/#testing-only).

## Cancelling

Pass an `AbortSignal` in the configuration. Aborting stops the collection and cancels the POST to your edge if it has started. `ready()` or `verify()` then rejects with the signal's abort reason, which is a `DOMException` named `AbortError` unless you pass your own reason to `abort()`. If the abort comes before the POST reaches your edge, no verdict is produced for that collection.

```js
const controller = new AbortController();
octet.start({ apiUrl: 'https://octet.example.com', sessionRef, signal: controller.signal });
window.addEventListener('pagehide', () => controller.abort());
```

## Errors

| Thrown or rejected with | When |
|---|---|
| `Error: octet: invalid apiUrl "..."` | `apiUrl` is not a URL. Thrown by `start()`, rejected by `verify()`. |
| `Error: octet: apiUrl must be https://...` | `apiUrl` uses another scheme. Thrown by `start()`, rejected by `verify()`. |
| `Error: octet: unknown mode "..."` | `mode` is not `'full'`, `'lite'` or `'passive'`. Thrown by `start()`, rejected by `verify()`. |
| `Error: octet: call start() before ready()` | `ready()` was called before `start()`. |
| `Error: signal report failed: <status>` | Your edge answered the POST with an error status. See [Errors](/docs/browser/reference/errors/). |
| `TypeError` from `fetch` | The POST to your edge failed at the network level, for example because of CORS or DNS. |
| `DOMException` named `AbortError`, or your own abort reason | The collection was aborted. |

A measurement that fails or is blocked never causes an error. The collector leaves it out and sends the rest.
