# TypeScript SDK

`@punchline-analytics/sdk` is a typed client for the API. Its methods and types are generated from the same [OpenAPI spec](https://www.punchlineanalytics.com/v1/openapi.json) the API is tested against, so they always match. It works in Node.js 18+, Deno, Bun and edge runtimes: anywhere with `fetch`. It has no dependencies.

```bash
npm install @punchline-analytics/sdk
```

MIT licensed. Every example in the [API reference](https://www.punchlineanalytics.com/docs/api-reference) is also a plain HTTP request, if you'd rather not add a dependency.

## Set up

```ts
import { Punchline } from "@punchline-analytics/sdk";

const punchline = new Punchline({ apiKey: process.env.PUNCHLINE_API_KEY! });
```

| Option | Default | |
|---|---|---|
| `apiKey` | (required) | A key from **Site settings → API and AI access** ([keys](https://www.punchlineanalytics.com/docs/api-auth)) |
| `baseUrl` | `https://app.punchlineanalytics.com` | |
| `maxRetries` | `2` | Retries after a `429`, waiting for `Retry-After` (up to 60 s each) |
| `fetch` | global `fetch` | Pass your own for proxies or tests |

The constructor checks the key's format, so a pasted-wrong key fails at startup instead of on the first request. Keep the key server-side ([why](https://www.punchlineanalytics.com/docs/api-auth#keeping-keys-safe)).

## Query

```ts
const { results, meta } = await punchline.query({
  site_id: process.env.PUNCHLINE_SITE_ID!,
  date_range: { from: "2026-09-01", to: "2026-09-30" },
  metrics: ["sessions", "agent_share"],
  dimensions: ["day"],
});

for (const row of results) console.log(row.day, row.sessions, row.agent_share);
if (meta.suppressed_rows) console.log(`${meta.suppressed_rows} small groups withheld for privacy`);
```

The [query cookbook](https://www.punchlineanalytics.com/docs/api-queries) has a body for most questions.

## Reports

```ts
const accounts = await punchline.getAccounts({ from: "2026-09-24", to: "2026-09-30", min_strength: "strong" });
const funnel = await punchline.getContentFunnel({ group_by: "content_type" });
const influence = await punchline.getAiInfluence({ goal: "Purchase" });
```

## Every agent session

`allSessions()` follows `next_cursor` for you:

```ts
for await (const session of punchline.allSessions({ from: "2026-09-24", to: "2026-09-30" })) {
  console.log(session.started_at, session.operator, session.type, session.paths);
}
```

## Sending events

From your backend, with a key that has **Can send events**:

```ts
await punchline.sendEvents(
  {
    site_id: process.env.PUNCHLINE_SITE_ID!,
    events: [{ name: "purchase", value: 249, path: "/checkout", visitor: { ip: req.ip, user_agent: req.headers["user-agent"] } }],
  },
  { idempotencyKey: `order-${order.id}` },
);
```

`visitor` ties the event to the visit it came from, so it's counted as a person or an agent like the visit was. The IP address and user agent are used in memory only and never stored. See [Events and goals](https://www.punchlineanalytics.com/docs/events-goals).

## Handoff links

```ts
const { data } = await punchline.createHandoff({
  site_id: process.env.PUNCHLINE_SITE_ID!,
  kind: "cart",
  value: cart.total,
  currency: "USD",
  url: `/cart/${cart.id}`,
  visitor: { ip: agentIp, user_agent: agentUserAgent },
});
// Return data.url to the agent.
```

See [Handoff links](https://www.punchlineanalytics.com/docs/handoffs).

## Managing alerts and goals

With a key that has **Can manage**:

```ts
const { data: alert } = await punchline.createAlert(
  { name: "Agent share spike", kind: "anomaly", metric: "agent_share", op: "up", value: 50 },
  { idempotencyKey: "agent-share-spike" },
);
await punchline.updateAlert(alert!.id!, { value: 80 });
```

## Errors

Every failed request throws a `PunchlineError`:

```ts
import { Punchline, PunchlineError } from "@punchline-analytics/sdk";

try {
  await punchline.query(body);
} catch (err) {
  if (err instanceof PunchlineError) {
    console.error(err.status, err.code, err.param, err.message); // 400 invalid_parameter limit "limit: must be at most 1000"
    if (err.code === "insufficient_scope") {
      /* this key can't do that */
    }
  } else throw err; // network errors
}
```

`err.code` is one of the [error codes](https://www.punchlineanalytics.com/docs/api-reference#errors); `err.docsUrl` links to its explanation. A `429` is retried before it throws.

## Methods

One method per endpoint, named after the endpoint's `operationId`:

| Method | Endpoint | Scope |
|---|---|---|
| `query(body)` | [`POST /v1/query`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-query) | `read` |
| `sendEvents(body, opts?)` | [`POST /v1/events`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-events) | `events:write` |
| `createHandoff(body)` | [`POST /v1/handoffs`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-handoffs) | `events:write` |
| `getHandoffs(params?)` | [`GET /v1/handoffs`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-handoffs) | `read` |
| `listSites()` | [`GET /v1/sites`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-sites) | `read` |
| `getSite(id)` | [`GET /v1/sites/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-sites-id) | `read` |
| `listOperators()` | [`GET /v1/operators`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-operators) | `read` |
| `listSessions(params?)` | [`GET /v1/sessions`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-sessions) | `read` |
| `getSession(id)` | [`GET /v1/sessions/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-sessions-id) | `read` |
| `getSessionEvidence(id)` | [`GET /v1/sessions/{id}/evidence`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-sessions-id-evidence) | `read` |
| `getContentFunnel(params?)` | [`GET /v1/content/funnel`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-content-funnel) | `read` |
| `getRecommendations(params?)` | [`GET /v1/content/recommendations`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-content-recommendations) | `read` |
| `getAccounts(params?)` | [`GET /v1/accounts`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-accounts) | `read` |
| `getAiInfluence(params?)` | [`GET /v1/ai-influence`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-ai-influence) | `read` |
| `getResolution(params?)` | [`GET /v1/resolution`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-resolution) | `read` |
| `getReconciliation(params?)` | [`GET /v1/reconciliation`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-reconciliation) | `read` |
| `listReports()` | [`GET /v1/reports`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-reports) | `read` |
| `createExport(body)` | [`POST /v1/exports`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-exports) | `read` |
| `getExport(id)` | [`GET /v1/exports/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-exports-id) | `read` |
| `downloadExport(id)` | [`GET /v1/exports/{id}/download`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-exports-id-download) | `read` |
| `listAlerts()` | [`GET /v1/alerts`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-alerts) | `admin` |
| `createAlert(body, opts?)` | [`POST /v1/alerts`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-alerts) | `admin` |
| `getAlert(id)` | [`GET /v1/alerts/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-alerts-id) | `admin` |
| `updateAlert(id, body, opts?)` | [`PATCH /v1/alerts/{id}`](https://www.punchlineanalytics.com/docs/api-reference#patch-v1-alerts-id) | `admin` |
| `deleteAlert(id, opts?)` | [`DELETE /v1/alerts/{id}`](https://www.punchlineanalytics.com/docs/api-reference#delete-v1-alerts-id) | `admin` |
| `listGoals()` | [`GET /v1/goals`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-goals) | `admin` |
| `createGoal(body, opts?)` | [`POST /v1/goals`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-goals) | `admin` |
| `getGoal(id)` | [`GET /v1/goals/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-goals-id) | `admin` |
| `updateGoal(id, body, opts?)` | [`PATCH /v1/goals/{id}`](https://www.punchlineanalytics.com/docs/api-reference#patch-v1-goals-id) | `admin` |
| `deleteGoal(id, opts?)` | [`DELETE /v1/goals/{id}`](https://www.punchlineanalytics.com/docs/api-reference#delete-v1-goals-id) | `admin` |
| `listTriggers()` | [`GET /v1/triggers`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-triggers) | `admin` |
| `createTrigger(body, opts?)` | [`POST /v1/triggers`](https://www.punchlineanalytics.com/docs/api-reference#post-v1-triggers) | `admin` |
| `getTrigger(id)` | [`GET /v1/triggers/{id}`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-triggers-id) | `admin` |
| `updateTrigger(id, body, opts?)` | [`PATCH /v1/triggers/{id}`](https://www.punchlineanalytics.com/docs/api-reference#patch-v1-triggers-id) | `admin` |
| `deleteTrigger(id, opts?)` | [`DELETE /v1/triggers/{id}`](https://www.punchlineanalytics.com/docs/api-reference#delete-v1-triggers-id) | `admin` |
| `getUrlParameters()` | [`GET /v1/url-parameters`](https://www.punchlineanalytics.com/docs/api-reference#get-v1-url-parameters) | `admin` |
| `updateUrlParameters(body, opts?)` | [`PATCH /v1/url-parameters`](https://www.punchlineanalytics.com/docs/api-reference#patch-v1-url-parameters) | `admin` |
| `deleteUrlParameters(opts?)` | [`DELETE /v1/url-parameters`](https://www.punchlineanalytics.com/docs/api-reference#delete-v1-url-parameters) | `admin` |
| `allSessions(params?)` | Every page of `GET /v1/sessions` | `read` |

Arguments: `id` is the path's ID, `params` the query-string parameters, `body` the JSON body, and `opts.idempotencyKey` sets `Idempotency-Key`. Every parameter and field is typed; your editor shows what each takes. The [API reference](https://www.punchlineanalytics.com/docs/api-reference) has the details and example responses.
