# SDK quickstart

> Install @hatched/sdk-js, authenticate, and make your first calls from Node or TypeScript.

Source: https://docs.hatched.live/docs/guides/sdk-quickstart

`@hatched/sdk-js` is the official TypeScript SDK for the Hatched API. It
ships as dual ESM + CJS, runs on Node 18+, Cloudflare Workers, Vercel
Edge, Deno, and Bun. Full package on
[npmjs.com/package/@hatched/sdk-js](https://npmjs.com/package/@hatched/sdk-js).

## Install

```bash
pnpm add @hatched/sdk-js
# or
npm install @hatched/sdk-js
# or
yarn add @hatched/sdk-js
# or
bun add @hatched/sdk-js
```

## Set the secret key as an environment variable

Add the key to your environment — never hard-code it.

```bash
# .env (Express), Vercel/Render dashboard, etc.
HATCHED_API_KEY=hatch_test_...
```

A literal key in source control is the most common Hatched onboarding
incident — it's the one mistake that's hard to undo (rotate the key, audit
access). The SDK reads the variable at construction; never log the key value.

For **Next.js App Router**, the key belongs in `.env.local` (not committed)
and is referenced from server components and route handlers only. Client
components that need to call Hatched should call your route handler, which
holds the secret.

## Initialise a client

```ts
import { HatchedClient } from '@hatched/sdk-js';

const hatched = new HatchedClient({
  apiKey: process.env.HATCHED_API_KEY!,
  // optional overrides:
  baseUrl: 'https://api.staging.hatched.live/api/v1',
  timeoutMs: 15_000,
  maxRetries: 3,
  fetch: globalThis.fetch,
});
```

`hatch_test_*` secret keys default to the staging API if you omit `baseUrl`;
`hatch_live_*` keys default to production.

The SDK parses the [canonical error envelope](/docs/reference/error-codes)
and throws typed `HatchedError` subclasses with `requestId`, `code`, and
`statusCode` fields.

> Secret keys (`hatch_live_*`, `hatch_test_*`) are **server-only**. The
> SDK throws if instantiated in a DOM environment. See
> [Auth model](/docs/concepts/auth-model) for browser options.

## Core resources

```ts
// Eggs & buddies
// Pass ensure: true on a first-run bootstrap so a stale egg is reused instead
// of throwing active_egg_limit — see /docs/guides/first-user-bootstrap.
const egg = await hatched.eggs.create({ userId, ensure: true });
await hatched.eggs.updateStatus(egg.eggId, 'ready');
await hatched.eggs.hatch(egg.eggId);
await hatched.buddies.list({ userId });

// Economy
await hatched.buddies.earn(buddyId, { amount: 50, reason: 'lesson_reward' });
await hatched.buddies.spend(buddyId, { amount: 20, reason: 'item_purchase' });
const equip = await hatched.buddies.equip(buddyId, { equip: [itemId] });
if (equip.operationId) await hatched.operations.wait(equip.operationId);
const buddy = await hatched.buddies.get(buddyId);
console.log(buddy.appearance?.status);
// Recovery only: use when buddy.appearance?.error?.code === 'needs_rerender'
// await hatched.buddies.rerenderAppearance(buddyId);

// Events
await hatched.events.send({ eventId, userId, type, properties });

// Operations (async image jobs)
const op = await hatched.eggs.hatch(egg.eggId);
const finished = await hatched.operations.wait(op.operationId);

// Widget sessions
await hatched.widgetSessions.create({ buddyId, userId, scopes, ttlSeconds });
await hatched.widgetSessions.revoke(sessionId);
```

## Error handling

```ts
import { HatchedError, RateLimitError, ValidationError } from '@hatched/sdk-js';

try {
  await hatched.events.send({ ... });
} catch (err) {
  if (err instanceof RateLimitError) {
    await sleep(err.retryAfter * 1000);
  } else if (err instanceof ValidationError) {
    console.error('bad payload:', err.details);
  } else if (err instanceof HatchedError) {
    console.error(err.code, err.requestId, err.message);
  } else {
    throw err;
  }
}
```

The SDK exposes a typed class per known error code. See
[Error codes](/docs/reference/error-codes) for the full catalogue.

## Retries and idempotency

- `events.send` is idempotent on `eventId` — passing the same id twice
  returns the cached effect without re-applying rules.
- Accepted events that do not change visible state include
  `effects.debugReason` (`no_active_buddies_for_user` or `no_matching_rules`)
  so your integration can show the right next step instead of treating the
  request as failed.
- The client retries `GET`s and `idempotent: true` `POST`s on network
  failures, `408`, `429` (with `Retry-After` honoured), and 5xx responses.
  Exponential backoff + jitter.
- 4xx responses (other than 408/429) surface immediately.

## Cancellation

Every resource method accepts an optional `AbortSignal` as its last
argument. The SDK combines it with the built-in timeout via
`AbortSignal.any`, so either source can cancel the request.

```ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 500);
await hatched.buddies.get(buddyId, controller.signal);
```

## Rate limits + request ids

```ts
await hatched.events.send({ ... });

console.log(hatched.getRateLimitInfo());
// { limit: 1000, remaining: 986, reset: 1735689600, retryAfter: undefined }

console.log(hatched.getLastRequestId());
// 'req_abc_123'
```

Include the request id in any support ticket and we can look up the full
trace.

## Next steps

- [Concepts overview](/docs/concepts/overview) — the motivational layer
  primitives (Mission Anchor, Hatch Ceremony, LEAGUES, the Octalysis Planner)
  the SDK is shaped around.
- [Widget integration guide](/docs/guides/widget-integration) — every widget
  surface, every prop, the full theming surface.
- [Auth model — secret vs publishable keys](/docs/concepts/auth-model) — the
  decision tree for when you reach for a publishable key instead of a secret
  key.
- [Webhooks](/docs/guides/handle-webhooks) — signature verification, retry
  semantics, and the event catalogue.
- [SDK reference](/docs/reference/sdk-js) — every resource, every method,
  every option.
- [Error codes](/docs/reference/error-codes) — the typed error catalogue and
  what to do about each one.
- [Use Hatched with AI coding assistants](/docs/ai-assistants) — drop the
  `AGENTS.md` into your repo, point your assistant at `/llms-full.txt`, and
  let it write the integration for you.
