SDK quickstart
Install @hatched/sdk-js, authenticate, and make your first calls from Node or TypeScript.
@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.
Install
pnpm add @hatched/sdk-js
# or
npm install @hatched/sdk-js
# or
yarn add @hatched/sdk-js
# or
bun add @hatched/sdk-jsSet the secret key as an environment variable
Add the key to your environment — never hard-code it.
# .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
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
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 for browser options.
Core resources
// 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
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 for the full catalogue.
Retries and idempotency
events.sendis idempotent oneventId— 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_userorno_matching_rules) so your integration can show the right next step instead of treating the request as failed. - The client retries
GETs andidempotent: truePOSTs on network failures,408,429(withRetry-Afterhonoured), 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.
const controller = new AbortController();
setTimeout(() => controller.abort(), 500);
await hatched.buddies.get(buddyId, controller.signal);Rate limits + request ids
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 — the motivational layer primitives (Mission Anchor, Hatch Ceremony, LEAGUES, the Octalysis Planner) the SDK is shaped around.
- Widget integration guide — every widget surface, every prop, the full theming surface.
- Auth model — secret vs publishable keys — the decision tree for when you reach for a publishable key instead of a secret key.
- Webhooks — signature verification, retry semantics, and the event catalogue.
- SDK reference — every resource, every method, every option.
- Error codes — the typed error catalogue and what to do about each one.
- Use Hatched with AI coding assistants — drop the
AGENTS.mdinto your repo, point your assistant at/llms-full.txt, and let it write the integration for you.