---
title: "SDK"
description: "@canarylytics/sdk is a typed, isomorphic client for the CanaryLytics REST API. It runs in browsers, Node, Workers and Electron, with optional React hooks for polling."
last_updated: "2026-07-30"
---

# SDK

`@canarylytics/sdk` is a typed, isomorphic client for the [REST API](/docs/api/rest/). It only needs a global `fetch`, so it runs unmodified in browsers, Node ≥ 18, Cloudflare Workers, and an Electron main process. Every wire type ships in the package itself (`ProjectInfo`, `StatsOverview`, `SessionDetail`, …), so responses are fully typed.

```bash
npm install @canarylytics/sdk
```

## Create a client

```ts

const lytics = createClient({
  apiKey: process.env.CANARYLYTICS_KEY!, // cly_live_… or cly_read_…
  baseUrl: "https://api.analytics.canarycoders.es", // optional, this is the default
});
```

`createClient` throws immediately if `apiKey` is missing, and again at call time if no global `fetch` is available and you didn't pass one via `config.fetch`.

## Call the API

Every method maps one-to-one onto a REST endpoint:

```ts
await lytics.project.info();

await lytics.stats.overview({ window: "7d" });
await lytics.stats.timeseries({ window: "30d" });
await lytics.stats.pages({ window: "7d" });
await lytics.stats.sources({ window: "7d" });
await lytics.stats.countries({ window: "7d" });
await lytics.stats.regions({ window: "7d", country: "ES" });
await lytics.stats.events({ window: "7d" });
await lytics.stats.utm({ window: "7d" });
await lytics.stats.devices({ window: "7d" });
await lytics.stats.goals({ window: "7d" });
await lytics.stats.realtime();

await lytics.sessions.list({ window: "24h", limit: 50 });
await lytics.sessions.get(sessionId); // null on 404, doesn't throw

await lytics.validate({ domain: "myapp.com", origin: "https://myapp.com" });
await lytics.exportEvents({ window: "7d", format: "ndjson" });

await lytics.events.track("signup", { plan: "pro" }, { path: "/onboarding" });
```

## Handle errors

Every non-2xx response throws a typed `LyticsApiError`:

```ts

try {
  await lytics.events.track("signup");
} catch (err) {
  if (err instanceof LyticsApiError) {
    // err.status (e.g. 403), err.code (e.g. "read_only_key"), err.message
    // 401 → code "unauthorized", message
    //   "A valid API key is required (Authorization: Bearer cly_live_…)"
  }
}
```

## Provision a project

Creating a project happens before any key exists, so it is a standalone function rather than a client method:

```ts

const { apiKey, project } = await provisionProject("https://api.analytics.canarycoders.es", {
  inviteCode: "...",
  name: "My App",
  domains: ["myapp.com"],
});
```

## React hooks

```bash
npm install @canarylytics/sdk react
```

```tsx

function Dashboard() {
  const client = useLyticsClient({ apiKey: readOnlyKey });
  const overview = useOverview(client, { window: "7d", refreshInterval: 5000 });
  const live = useRealtime(client, { refreshInterval: 2000 });

  if (overview.isLoading) return <p>Loading…</p>;
  if (overview.error) return <p>{overview.error.message}</p>;
  return <p>{overview.data?.totals.sessions} sessions · {live.data?.activeSessions} live</p>;
}
```

Every hook (`useOverview`, `useTimeseries`, `useTopPages`, `useSources`, `useCountries`, `useCustomEvents`, `useUtm`, `useDevices`, `useGoals`, `useRealtime`, `useSessions`) returns `{ data, error, isLoading, refresh }` and accepts the matching stats query plus `refreshInterval` (milliseconds; omit or pass `0` to fetch once). Polling pauses while the tab is hidden and refetches immediately when it becomes visible again, so data doesn't go stale in a backgrounded tab.

`react >= 18` is an optional peer dependency, needed only if you import from the `/react` subpath.

<Aside type="note">
  Import from `"@canarylytics/sdk"` for the core client and `"@canarylytics/sdk/react"` for the hooks. They are separate entry points, so a non-React consumer such as a server script or an Electron main process doesn't need React installed at all.
</Aside>

## Related

<CardGrid>
  <LinkCard title="REST API" description="The endpoints behind every SDK method." href="/docs/api/rest/" />
  <LinkCard title="Custom events & goals" description="Track conversions with events.track()." href="/docs/tracking/events-and-goals/" />
</CardGrid>

## Sitemap

- [Docs sitemap](https://analytics.canarycoders.es/docs/sitemap.md)
- [Full documentation as a single file](https://analytics.canarycoders.es/llms-full.txt)
