SDK (JavaScript / TypeScript)
Complete method reference for @hatched/sdk-js — HatchedClient, resources, error classes.
Package: @hatched/sdk-js
pnpm add @hatched/sdk-jsHatchedClient
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 | nullRequest 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 = {}): booleanVerifies 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>;
}