# Manual setup — Node (/docs/get-started/manual-node)



Add TokenPolice to a Node / TypeScript app in three steps. You'll need your
[API key and the collector URL](/docs/get-started/api-key) first.

## 1. Install [#1-install]

```bash
npm install token-police
```

Node 20 or newer; works in both ESM and CommonJS. Provider libraries are auto-instrumented —
no extra install for the common ones. Framework extras are on the
[integrations](/docs/integrations/matrix) page.

## 2. Initialize [#2-initialize]

Call `tp.init()` **once at startup, before you construct your LLM client** — and pass the
client class in `instrumentModules`:

```typescript
import * as tp from "token-police";
import OpenAI from "openai";                        // default export or namespace — both work

tp.init({
  apiKey: process.env.TOKENPOLICE_API_KEY,          // tp_sk_...
  baseUrl: "https://collect.tokenpolice.ai",        // the collector — always set this
  firewall: "dry_run",                              // watch first; flip to "enforce" later
  instrumentModules: { openAI: OpenAI },
});

// Only now construct the client:
const client = new OpenAI();
```

Every call `client` makes is now checked before it runs and logged after.

<Callout type="warn">
  **Always pass `instrumentModules`.** It's required on native ESM, on `openai` 7.x, and on
  pnpm/workspace installs — and harmless everywhere else. The full key list and which import
  form each one takes are in [Node & ESM](/docs/sdk/node-esm).
</Callout>

## 3. Attach identity (recommended) [#3-attach-identity-recommended]

To budget per user or per session, wrap your entrypoint with `tp.workflow`. It tags every
call inside with one identity:

```typescript
const runAgent = tp.workflow(
  { name: "support_agent" },
  async (opts: { userId: string; paidPlan: string; sessionId: string; query: string }) => {
    return client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: opts.query }],
    });
  },
);

await runAgent({ userId: "u_123", paidPlan: "pro", sessionId: "chat_42", query });
```

`tp.workflow` reads `userId`, `paidPlan`, and `sessionId` from the **first argument when it's
an object** (snake\_case keys work too). These identities are what budgets and rules match
on. More in [Identity](/docs/concepts/identity).

## 4. Run it [#4-run-it]

Run your app and make an LLM call. Then open
[app.tokenpolice.ai](https://app.tokenpolice.ai) — the call shows up with its token counts
and cost. Only token metadata is sent; your prompts and completions never leave your process
([what's in that record](/docs/concepts/data-privacy)).

## Then finish the job [#then-finish-the-job]

The three steps above get you metering. A production integration also names its steps, wraps
its tool calls, flushes before a serverless function freezes, and handles a blocked call —
and then proves all of it works. The
[integration checklist](/docs/get-started/integration-checklist) walks through the rest in
order.

## Next steps [#next-steps]

<Cards>
  <Card title="Node & ESM" href="/docs/sdk/node-esm" description="instrumentModules, import order, per-key import form." />

  <Card title="init() parameters" href="/docs/sdk/init" description="Every option, defaults, and env vars." />

  <Card title="Dry-run vs enforce" href="/docs/concepts/dry-run-vs-enforce" description="Turn on blocking when you're ready." />

  <Card title="Integration checklist" href="/docs/get-started/integration-checklist" description="The rest of the steps, and how to verify." />

  <Card title="ESM / instrumentation order" href="/docs/troubleshooting/esm-instrumentation-order" description="If calls aren't showing up." />

  <Card title="See it in a running app →" href="/docs/sample-apps/support-agent" description="A full support-desk chat wired exactly like this, in Node and Python." />
</Cards>
