HatchedDocs
Reference

SDK (JavaScript / TypeScript)

Complete method reference for @hatched/sdk-js — HatchedClient, resources, error classes.

Package: @hatched/sdk-js

pnpm add @hatched/sdk-js

HatchedClient

Official Hatched SDK client for JavaScript/TypeScript.

Server-side (secret key):

const hatched = new HatchedClient({
  apiKey: process.env.HATCHED_API_KEY!,
});
const egg = await hatched.eggs.create({ userId: 'user_123' });

Browser (publishable key, scoped):

const hatched = new HatchedClient({
  publishableKey: 'hatch_pk_xxxxxxxx',
});
const buddy = await hatched.buddies.get('bdy_abc');

HatchedClient.health()

health()

Health check; returns API status metadata.

HatchedClient.getRateLimitInfo()

getRateLimitInfo()

Latest X-RateLimit-* snapshot from the most recent response.

HatchedClient.getLastRequestId()

getLastRequestId(): string | null

Request id of the most recent response (for support correlation).

EggsResource

EggsResource.create()

create(params: CreateEggParams, signal?: AbortSignal): Promise<Egg>

Creates a new pending egg bound to an external user.

@example

const egg = await hatched.eggs.create({ userId: 'user_42' });

EggsResource.get()

get(eggId: string, signal?: AbortSignal): Promise<Egg>

Fetches the canonical state of a single egg.

EggsResource.list()

list(params: ListEggsParams = {}): Promise<Egg[]>

Lists eggs with optional filters.

EggsResource.updateStatus()

updateStatus(eggId: string, status: 'ready' | 'cancelled', signal?: AbortSignal): Promise<EggStatusChange>

Transitions an egg to ready or cancelled. The API only permits ready and cancelled terminal statuses via this endpoint.

EggsResource.hatch()

hatch(eggId: string, signal?: AbortSignal): Promise<HatchResult>

Kicks off an async hatch operation. Poll the returned operationId via operations.wait() to resolve when the buddy art is ready.

BuddiesResource

BuddiesResource.get()

get(buddyId: string, signal?: AbortSignal): Promise<Buddy>

Fetches a buddy by id.

BuddiesResource.list()

list(params: BuddyListParams = {}): Promise<BuddyList>

Lists buddies with optional filters.

BuddiesResource.updateName()

updateName(buddyId: string, name: string, signal?: AbortSignal): Promise<Buddy>

BuddiesResource.archive()

archive(buddyId: string, signal?: AbortSignal): Promise<Buddy>

BuddiesResource.updateSkills()

updateSkills(buddyId: string, updates: SkillUpdate[], signal?: AbortSignal)

BuddiesResource.earn()

earn(buddyId: string, params: EarnCoinsParams, idempotencyKey?: string, signal?: AbortSignal)

Adds coins to a buddy's ledger for a given reason. Alias: BuddiesResource.earnCoins.

BuddiesResource.spend()

spend(buddyId: string, params: SpendCoinsParams, idempotencyKey?: string, signal?: AbortSignal)

Debits coins from a buddy's ledger. Fails with InsufficientBalanceError if the buddy doesn't have enough.

BuddiesResource.awardBadge()

awardBadge(buddyId: string, badgeKey: string, reason?: string, signal?: AbortSignal)

BuddiesResource.getBadges()

getBadges(buddyId: string, signal?: AbortSignal)

BuddiesResource.equip()

equip(buddyId: string, params: EquipItemsParams, signal?: AbortSignal)

Equips or unequips items on a buddy.

BuddiesResource.purchaseItem()

purchaseItem(buddyId: string, itemId: string, idempotencyKey?: string, signal?: AbortSignal)

BuddiesResource.getPurchasedItems()

getPurchasedItems(buddyId: string, signal?: AbortSignal)

BuddiesResource.getEvolution()

getEvolution(buddyId: string, signal?: AbortSignal)

BuddiesResource.evolve()

evolve(buddyId: string, signal?: AbortSignal)

BuddiesResource.getProgression()

getProgression(buddyId: string, signal?: AbortSignal)

BuddiesResource.tokens()

tokens(buddyId: string, signal?: AbortSignal): Promise<TokensSummary>

Typed token balances for a buddy, grouped into primary (spendable) and progression (accumulate-only). Returns null for either slot if the customer has not configured that kind.

BuddiesResource.evolutions()

evolutions(buddyId: string, params: { page?: number; limit?: number; signal?: AbortSignal } = {}): Promise<{
    data: BuddyEvolutionRecord[];
    pagination: { page: number; limit: number; total: number };
  }>

Paginated stage-transition timeline for a buddy (includes both prod and demo evolutions).

BuddiesResource.getUserSummary()

getUserSummary(userId: string, signal?: AbortSignal)

EventsResource

EventsResource.send()

send(params: SendEventParams, signal?: AbortSignal): Promise<EventEffects>

Ingests a domain event. The same eventId returning twice yields the cached effect without re-applying rules.

@example

const effects = await hatched.events.send({
  eventId: 'lesson_lsn_42_user_123',
  userId: 'user_123',
  type: 'lesson_completed',
  properties: { score: 94 },
});

EventsResource.sendBatch()

sendBatch(events: SendEventParams[], signal?: AbortSignal): Promise<{ results: EventEffects[] }>

Sends a batch of events in a single call.

OperationsResource

OperationsResource.get()

get(operationId: string, signal?: AbortSignal): Promise<Operation<TResult>>

Fetches an operation's current status.

OperationsResource.wait()

wait(operationId: string, options: WaitOptions = {}): Promise<Operation<TResult>>

Polls an operation until it reaches completed or failed.

@throws Error if the operation doesn't finish before timeoutMs elapses. @example

const op = await hatched.eggs.hatch(egg.eggId);
const finished = await hatched.operations.wait(op.operationId);

OperationsResource.waitForCompletion()

waitForCompletion(operationId: string, options: { timeout?: number; interval?: number; signal?: AbortSignal } = {}): Promise<Operation<TResult>>

@deprecated Use OperationsResource.wait instead.

WidgetSessionsResource

WidgetSessionsResource.create()

create(params: CreateSessionParams, signal?: AbortSignal): Promise<SessionToken>

Mints a short-lived widget session token for browser/interactive widgets. Never ship a secret API key to the browser — always go through this endpoint.

WidgetSessionsResource.revoke()

revoke(sessionId: string, signal?: AbortSignal): Promise<void>

EmbedTokensResource

EmbedTokensResource.create()

create(params: CreateEmbedTokenParams, signal?: AbortSignal): Promise<EmbedToken>

Mints a signed token for a read-only embedded widget.

WebhooksResource

WebhooksResource.list()

list(signal?: AbortSignal): Promise<WebhookEndpoint[]>

Lists webhook endpoints registered for the current customer.

WebhooksResource.create()

create(params: CreateWebhookParams, signal?: AbortSignal): Promise<WebhookEndpoint>

Registers a new webhook endpoint.

WebhooksResource.delete()

delete(endpointId: string, signal?: AbortSignal): Promise<void>

Deletes a webhook endpoint.

WebhooksResource.deliveries()

deliveries(params: ListDeliveriesParams): Promise<Page<WebhookDelivery>>

Lists recent deliveries for a given endpoint.

WebhooksResource.replay()

replay(deliveryId: string, signal?: AbortSignal): Promise<WebhookDelivery>

Replays a specific delivery attempt.

WebhooksResource.verifySignature()

static verifySignature(rawBody: string | Buffer, signatureHeader: string, secret: string, options: VerifySignatureOptions = {}): boolean

Verifies the Hatched-Signature header for a webhook payload.

The signature format is t=<unix_ts>,v1=<hmac_sha256_hex>. Pass the raw request body bytes (not the parsed JSON) — any reformatting will invalidate the signature.

@example

const valid = WebhooksResource.verifySignature(rawBody, header, process.env.HATCHED_WEBHOOK_SECRET!);
if (!valid) return new Response('invalid signature', { status: 400 });

HatchedError

Base class for every error raised by the Hatched SDK. Every subclass carries the HTTP status, stable error code, optional details payload, and the request id the API echoed back for support correlation.

No public methods.

AuthError

Shared base for 401/403 responses.

No public methods.

UnauthorizedError

No public methods.

ForbiddenError

No public methods.

PublishableKeyScopeError

Raised when a request uses a publishable key for an operation the publishable key is not scoped for (e.g. mutation endpoints).

No public methods.

NotFoundError

No public methods.

ValidationError

No public methods.

RateLimitError

No public methods.

InsufficientBalanceError

No public methods.

TooManyItemsError

Raised when an equip request asks the buddy to wear more items than the image compositing pipeline can reliably render. The current cap is four — the SDK surfaces max and attempted on details so callers can show a precise error to the end-user.

No public methods.

CategoryConflictError

Raised when an equip request tries to put two items in the same category slot (e.g. two head items). Only the accessory category allows stacking.

No public methods.

ConflictError

No public methods.

ConfigVersionMismatchError

Raised when a buddy is pinned to a config version that does not match the one the caller expected (e.g. after a migration race).

No public methods.

UpstreamImageError

No public methods.

CreditInsufficientError

Raised when an AI / generative request cannot be authorised because the customer has no available credits across any pool. The details object includes the amount required and what remains in each pool, plus a URL the caller can redirect to so the end-customer can top up.

No public methods.

EventQuotaExceededError

Raised when a POST /events call would push the customer over the monthly event quota allowed by their plan. reset_at is an ISO timestamp for the first of the next UTC month; callers should back off until then or upgrade.

No public methods.

PlanFeatureLockedError

Raised when the customer's plan does not include the requested feature (e.g. a Free tier customer trying to use the marketplace API). The SDK surfaces which plan is required so callers can prompt an upgrade.

No public methods.

GatesResource

Generic spend-to-unlock primitive. Customers define gates in their dashboard (gate_key, token_key, cost, metadata). A buddy calls unlock to spend the configured token cost and flip the gate open — idempotent: repeat calls return alreadyUnlocked: true without touching the economy.

GatesResource.list()

list(signal?: AbortSignal): Promise<{ gates: TokenGate[] }>

Lists gates configured on this customer. Secret-key only.

GatesResource.unlock()

unlock(buddyId: string, gateKey: string, signal?: AbortSignal): Promise<UnlockResult>

Buddy spends gate.cost of gate.tokenKey to unlock gateKey. Fails with InsufficientBalanceError if the buddy lacks tokens and with ValidationError('progression_not_spendable') if the gate references a progression token.

GatesResource.unlocks()

unlocks(buddyId: string, signal?: AbortSignal): Promise<{ unlocks: BuddyUnlock[] }>

List gates a buddy has unlocked.

Types

CreditInsufficientDetails

export interface CreditInsufficientDetails {
  required?: number;
  available?: number;
  welcome?: number;
  paid?: number;
  promo?: number;
  upgrade_url?: string;
  top_up_url?: string;
}

EventQuotaExceededDetails

export interface EventQuotaExceededDetails {
  used?: number;
  limit?: number;
  reset_at?: string;
  upgrade_url?: string;
}

PlanFeatureLockedDetails

export interface PlanFeatureLockedDetails {
  feature?: string;
  required_plan?: string;
  current_plan?: string;
  upgrade_url?: string;
}

EggStatus

export type EggStatus = 'waiting' | 'ready' | 'hatching' | 'hatched' | 'cancelled';

CreateEggParams

export interface CreateEggParams {
  /** The external user id that owns the egg. */
  userId: string;
  /** Optional preset seed used by the config engine. */
  presetId?: string;
  /** Free-form metadata attached to the egg. */
  metadata?: Record<string, unknown>;
}

Egg

export interface Egg {
  eggId: string;
  userId: string;
  status: EggStatus;
  visualVariant: number;
  configVersionId: string;
  metadata: Record<string, unknown>;
  createdAt: string;
}

EggStatusChange

export interface EggStatusChange {
  eggId: string;
  status: EggStatus;
  previousStatus: EggStatus;
}

HatchResult

export interface HatchResult {
  operationId: string;
  status: string;
}

ListEggsParams

export interface ListEggsParams {
  userId?: string;
  status?: EggStatus;
  page?: number;
  limit?: number;
  signal?: AbortSignal;
}

Buddy

export interface Buddy {
  id: string;
  customerId: string;
  userId: string;
  audience: string;
  name: string;
  configVersionId: string;
  evolutionStage: number;
  coins: number;
  status: 'active' | 'archived';
  skills: Record<string, number>;
  tokens: Record<string, number>;
  imageUrl: string | null;
  thumbUrl: string | null;
  equippedItems: string[];
  createdAt: string;
  updatedAt: string;
}

BuddyListParams

export interface BuddyListParams {
  userId?: string;
  status?: string;
  evolutionStage?: number;
  page?: number;
  limit?: number;
  sort?: string;
  order?: 'asc' | 'desc';
  signal?: AbortSignal;
}

SkillUpdate

export interface SkillUpdate {
  key: string;
  action: 'increase' | 'decrease' | 'set';
  amount?: number;
  value?: number;
}

EarnCoinsParams

export interface EarnCoinsParams {
  amount: number;
  reason: string;
  referenceId?: string;
  /**
   * Token key to earn. Defaults to the customer's `primary` token. Passing
   * a progression token grants progress; passing any other configured token
   * key is accepted by the rule engine. Omit to earn the default coin / primary.
   */
  token?: string;
}

SpendCoinsParams

export interface SpendCoinsParams {
  amount: number;
  reason: string;
  itemId?: string;
  /**
   * Token key to spend. Defaults to the customer's `primary` token. Spending
   * a progression token fails with ValidationError('progression_not_spendable').
   */
  token?: string;
}

TokenBalance

export interface TokenBalance {
  /** Canonical `primary` or `progression` identifier. */
  kind: 'primary' | 'progression';
  /** Customer-defined token key (e.g. `gems`, `xp`). */
  key: string;
  /** Human-readable label for display. */
  label: string;
  /** Current balance. */
  balance: number;
  /** Lifetime earned (earn ledger sum). */
  lifetimeEarned: number;
  /** Lifetime spent (spend ledger sum) — always 0 for progression. */
  lifetimeSpent: number;
}

TokensSummary

export interface TokensSummary {
  primary: TokenBalance | null;
  progression: TokenBalance | null;
}

BuddyEvolutionRecord

export interface BuddyEvolutionRecord {
  id: string;
  buddyId: string;
  fromStage: number;
  toStage: number;
  triggeredByEventId: string | null;
  imageUrl: string | null;
  source: 'prod' | 'demo' | 'auto';
  metadata: Record<string, unknown>;
  occurredAt: string;
}

EquipItemsParams

export interface EquipItemsParams {
  equip?: string[];
  unequip?: string[];
}

BuddyList

export interface BuddyList {
  data: Buddy[];
  meta: { total: number; page: number; limit: number };
}

SendEventParams

export interface SendEventParams {
  /** Stable id used for idempotency. Re-sending the same eventId is a no-op. */
  eventId: string;
  /** External user id the event belongs to. */
  userId: string;
  /** Event type (e.g. `lesson_completed`, `workout_finished`). */
  type: string;
  /** When the event occurred. Defaults to "now" if omitted. */
  occurredAt?: Date | string;
  /** Arbitrary key-value payload forwarded to the rule engine. */
  properties?: Record<string, unknown>;
}

EventEffects

export interface EventEffects {
  coins?: number;
  badgesAwarded?: string[];
  badgesReady?: string[];
  tokens?: string[];
  evolutionReady?: boolean;
  streakMilestones?: number[];
}

OperationStatus

export type OperationStatus = 'pending' | 'processing' | 'completed' | 'failed';

Operation

export interface Operation<TResult = unknown> {
  operationId: string;
  /** Alias for {@link Operation.operationId} for callers that prefer `id`. */
  id: string;
  type: string;
  status: OperationStatus;
  result?: TResult;
  error?: string;
  createdAt: string;
  updatedAt: string;
}

WaitOptions

export interface WaitOptions {
  /** Maximum total time to wait, in milliseconds. Default 30_000. */
  timeoutMs?: number;
  /** Poll interval, in milliseconds. Default 2000. */
  intervalMs?: number;
  /** External abort signal. */
  signal?: AbortSignal;
}

CreateSessionParams

export interface CreateSessionParams {
  buddyId: string;
  userId: string;
  scopes: string[];
  ttlSeconds?: number;
}

SessionToken

export interface SessionToken {
  token: string;
  sessionId: string;
  expiresAt: string;
  scopes: string[];
}

CreateEmbedTokenParams

export interface CreateEmbedTokenParams {
  buddyId: string;
  userId: string;
  ttlSeconds?: number;
}

EmbedToken

export interface EmbedToken {
  token: string;
  expiresAt: string;
  mode: 'read-only';
}

WebhookEndpoint

export interface WebhookEndpoint {
  id: string;
  url: string;
  events: string[];
  status: 'active' | 'paused';
  secret: string;
  createdAt: string;
  updatedAt: string;
}

CreateWebhookParams

export interface CreateWebhookParams {
  url: string;
  events: string[];
  description?: string;
}

WebhookDelivery

export interface WebhookDelivery {
  id: string;
  endpointId: string;
  event: string;
  status: 'pending' | 'succeeded' | 'failed';
  responseStatus?: number;
  attempts: number;
  createdAt: string;
  lastAttemptAt?: string;
}

ListDeliveriesParams

export interface ListDeliveriesParams {
  endpointId: string;
  status?: 'pending' | 'succeeded' | 'failed';
  cursor?: string;
  limit?: number;
  signal?: AbortSignal;
}

Page

export interface Page<T> {
  data: T[];
  nextCursor: string | null;
}

VerifySignatureOptions

export interface VerifySignatureOptions {
  /** Maximum clock-skew in seconds. Defaults to 5 minutes. */
  toleranceSeconds?: number;
  /** Clock used for timestamp validation — useful in tests. */
  now?: () => number;
}

TokenGate

export interface TokenGate {
  id: string;
  gateKey: string;
  tokenKey: string;
  cost: number;
  label: string | null;
  description: string | null;
  metadata: Record<string, unknown>;
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
}

UnlockResult

export interface UnlockResult {
  gateKey: string;
  unlocked: true;
  alreadyUnlocked: boolean;
  unlockedAt: string;
  balanceAfter: number | null;
  metadata: Record<string, unknown>;
}

BuddyUnlock

export interface BuddyUnlock {
  gateKey: string;
  unlockedAt: string;
  metadata: Record<string, unknown>;
}

On this page

HatchedClientHatchedClient.health()HatchedClient.getRateLimitInfo()HatchedClient.getLastRequestId()EggsResourceEggsResource.create()EggsResource.get()EggsResource.list()EggsResource.updateStatus()EggsResource.hatch()BuddiesResourceBuddiesResource.get()BuddiesResource.list()BuddiesResource.updateName()BuddiesResource.archive()BuddiesResource.updateSkills()BuddiesResource.earn()BuddiesResource.spend()BuddiesResource.awardBadge()BuddiesResource.getBadges()BuddiesResource.equip()BuddiesResource.purchaseItem()BuddiesResource.getPurchasedItems()BuddiesResource.getEvolution()BuddiesResource.evolve()BuddiesResource.getProgression()BuddiesResource.tokens()BuddiesResource.evolutions()BuddiesResource.getUserSummary()EventsResourceEventsResource.send()EventsResource.sendBatch()OperationsResourceOperationsResource.get()OperationsResource.wait()OperationsResource.waitForCompletion()WidgetSessionsResourceWidgetSessionsResource.create()WidgetSessionsResource.revoke()EmbedTokensResourceEmbedTokensResource.create()WebhooksResourceWebhooksResource.list()WebhooksResource.create()WebhooksResource.delete()WebhooksResource.deliveries()WebhooksResource.replay()WebhooksResource.verifySignature()HatchedErrorAuthErrorUnauthorizedErrorForbiddenErrorPublishableKeyScopeErrorNotFoundErrorValidationErrorRateLimitErrorInsufficientBalanceErrorTooManyItemsErrorCategoryConflictErrorConflictErrorConfigVersionMismatchErrorUpstreamImageErrorCreditInsufficientErrorEventQuotaExceededErrorPlanFeatureLockedErrorGatesResourceGatesResource.list()GatesResource.unlock()GatesResource.unlocks()TypesCreditInsufficientDetailsEventQuotaExceededDetailsPlanFeatureLockedDetailsEggStatusCreateEggParamsEggEggStatusChangeHatchResultListEggsParamsBuddyBuddyListParamsSkillUpdateEarnCoinsParamsSpendCoinsParamsTokenBalanceTokensSummaryBuddyEvolutionRecordEquipItemsParamsBuddyListSendEventParamsEventEffectsOperationStatusOperationWaitOptionsCreateSessionParamsSessionTokenCreateEmbedTokenParamsEmbedTokenWebhookEndpointCreateWebhookParamsWebhookDeliveryListDeliveriesParamsPageVerifySignatureOptionsTokenGateUnlockResultBuddyUnlockRelated