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

# Advanced

# Advanced usage

## Custom `fetch`

Inject any `fetch`-compatible function to add telemetry, retries on non-credential endpoints, DNS pinning, etc.

```ts theme={null}
import { Blursec } from "@blursec/sdk";

const tracingFetch: typeof fetch = async (input, init) => {
  const url = typeof input === "string" ? input : input.toString();
  const start = performance.now();
  try {
    const response = await fetch(input, init);
    metrics.histogram("blursec.fetch.ms", performance.now() - start, {
      status: response.status,
      url: stripQuery(url),
    });
    return response;
  } catch (err) {
    metrics.increment("blursec.fetch.error");
    throw err;
  }
};

const blursec = new Blursec({
  apiKey: process.env.BLURSEC_API_KEY!,
  fetch: tracingFetch,
});
```

***

## Why you should **not** retry `checkCredential`

It's tempting to wrap `checkCredential` in an exponential-backoff retry loop. **Don't.**

The Blursec model is: "answer in under 50 ms or step out of the way." If the API is slow, the right answer is to **fail open immediately** so the user's login isn't delayed. Retries:

1. **Multiply the latency cost** of an outage — three attempts at 1500 ms each is 4.5 seconds added to every login.
2. **Add load to an already-struggling API** — when Blursec is degraded, the worst possible thing every customer can do is retry.
3. **Don't change the outcome** — if a check times out once during a 30-second incident, it's likely to time out again on the next 1-2 attempts.

The fail-open contract exists *because* retries are the wrong reflex. If you want stricter behavior, lower `timeoutMs` — don't add retries.

```ts theme={null}
// ❌ DO NOT do this
async function checkWithRetry(email: string, password: string) {
  for (let i = 0; i < 3; i++) {
    try {
      return await blursec.credentials.check(email, password, {
        failOpen: false,
        timeoutMs: 1500,
      });
    } catch {
      await sleep(100 * 2 ** i);
    }
  }
  // ...what now? you've added 3 seconds of latency for nothing.
}

// ✅ Do this instead
const risk = await blursec.credentials.check(email, password, {
  timeoutMs: 500,   // tight budget, but fails open if blown
});
```

For `auth.*` and `sessions.kill()`, retries are reasonable — those are infrequent ops on the control plane. Wrap them in your `fetch` if you want:

```ts theme={null}
const retryingFetch: typeof fetch = async (input, init) => {
  const url = typeof input === "string" ? input : input.toString();
  if (url.includes("/credentials/check")) {
    return fetch(input, init); // never retry credential checks
  }

  let lastErr: unknown;
  for (let i = 0; i < 3; i++) {
    try {
      const r = await fetch(input, init);
      if (r.status < 500) return r;
      lastErr = new Error(`status ${r.status}`);
    } catch (err) {
      lastErr = err;
    }
    await new Promise((r) => setTimeout(r, 100 * 2 ** i));
  }
  throw lastErr;
};
```

***

## Telemetry

Every `CredentialRiskResult` includes `checkedAt` and `durationMs`, so you can record the SDK's view of latency without instrumenting `fetch`:

```ts theme={null}
const risk = await blursec.checkCredential(email, password);

metrics.histogram("blursec.check.ms", risk.durationMs, {
  failed_open: String(risk.failedOpen),
  leaked: String(risk.leaked),
  severity: risk.severity ?? "none",
});
```

For deeper visibility, instrument the injected `fetch` (see above).

***

## Cancellation

Every method accepts an `AbortSignal` that composes with the SDK's internal timeout:

```ts theme={null}
const controller = new AbortController();
req.on("close", () => controller.abort()); // client disconnected

const risk = await blursec.credentials.check(email, password, {
  signal: controller.signal,
  timeoutMs: 1000,
});
```

If the caller's signal aborts first, the SDK throws a `BlursecTimeoutError` with `cause` set to the abort reason. If the internal timeout fires first, the timeout error carries `timeoutMs`.

***

## Edge runtimes

The SDK is shipped with `"platform": "neutral"` and targets `es2022`, so it runs unmodified on:

* **Cloudflare Workers** — `import { Blursec } from "@blursec/sdk"` works. The Workers runtime provides `fetch`, `crypto.subtle`, and `AbortController` natively.
* **Vercel Edge Functions** — same as Workers.
* **Deno** — `import { Blursec } from "npm:@blursec/sdk"`.
* **Bun** — `bun add @blursec/sdk`.

No polyfills needed.

See [examples/cloudflare-worker.ts](https://github.com/blur-sec/blursec-sdk/blob/main/examples/cloudflare-worker.ts) for a complete edge integration.

***

## Custom `severity → action` maps per route

The default mapping is conservative — block on `critical`, force-reset on `high`/`medium`, monitor on `low`. You can override per-call, which is useful when different routes warrant different strictness:

```ts theme={null}
// /admin/login — pin everything stricter
const adminMap = {
  low: "force_reset",
  medium: "block",
  high: "block",
  critical: "block",
} as const;

app.post("/admin/login", async (req, res) => {
  const risk = await blursec.checkCredential(req.body.email, req.body.password, {
    severityActions: adminMap,
  });

  if (risk.recommendedAction !== "allow") {
    return res.status(403).json({ error: "credential_risk", risk });
  }
});
```

***

## Manual hashing for non-standard transports

If you need to talk to the Blursec API through a transport the SDK doesn't support (e.g. a gRPC bridge, a CDN-mirrored prefix endpoint), use the exported hash helpers and call `checkHash` (or skip the SDK entirely):

```ts theme={null}
import { hashCredential, matchesHash, HASH_PREFIX_LENGTH } from "@blursec/sdk";

const { full, prefix } = await hashCredential(email, password);
// → prefix is `full.slice(0, HASH_PREFIX_LENGTH)`

const matches = await myTransport.getRange(prefix);
const leaked = matches.some((m) => matchesHash(full, m.hashSuffix));
```

See [examples/k-anonymity.ts](https://github.com/blur-sec/blursec-sdk/blob/main/examples/k-anonymity.ts).
