# Changelog

> Release notes for @hatched/sdk-js — mirrored from the package's CHANGELOG.md.

Source: https://docs.hatched.live/docs/reference/changelog

{/* AUTO-MIRRORED from packages/sdk-js/CHANGELOG.md by apps/docs/scripts/generate-changelog.ts. */}

Release notes for `@hatched/sdk-js`. Produced by [changesets](https://github.com/changesets/changesets) on every merge to `main`.

# @hatched/sdk-js

## 1.2.0

### Minor Changes

- 48e9fdd: eggs.create now accepts a first-class audience option that binds the egg (and the buddy it hatches into) to a named audience. Audience-scoped content — streaks, badges, marketplace items — is keyed by audience, so a buddy born in the wrong audience makes those widgets 404 or render empty. The option is shorthand for metadata.audience and is folded in before the request, so the intuitive create(\{ userId, audience }) call sends the shape the API expects instead of being rejected by strict body validation. Single-audience workspaces can omit it; multi-audience workspaces should set it during the first-run bootstrap.

## 1.1.3

### Patch Changes

- fd3aff9: Refresh the generated OpenAPI types to cover two new dashboard endpoints: hosted-surface logo upload (`POST /customers/me/hosted-surfaces/{id}/logo`) and the admin event-trigger (`POST /events/admin-trigger`). This is a type-only refresh — no SDK resource methods or runtime behavior changed.

## 1.1.2

### Patch Changes

- e67d739: Analytics endpoints (engagement, activity-summary, economy-summary, economy-health, popular-items, popular-badges, retention, roi-metrics) now accept an optional `audience` query parameter, so metrics can be scoped to a single audience instead of the whole workspace. Omitting it returns the workspace-wide numbers as before.
- e67d739: Generated API types now reflect the documented error contract for embed/session token creation and config-version publishing: a 404 is returned when the supplied buddy does not exist for the tenant, publishing an empty draft returns a 400, and the "already published" publish conflict is described more precisely (409 for a non-draft version). The new `GET /billing/checkout/session/{id}` checkout-reconcile endpoint is also now described, so a delayed or dropped Stripe webhook can be confirmed synchronously on dashboard return. A new `GET /customers/me/hosted-surfaces/{id}/players/{playerId}/access-code` endpoint lets a tenant admin re-view an existing hosted-surface player's access code and QR token without rotating them. These are additive response/endpoint-type refinements — existing request and success types are unchanged.

## 1.1.1

### Patch Changes

- a54b74c: Regenerated API types now cover the dashboard password endpoints (`POST /auth/password/change`, `POST /auth/password/reset/request`, `POST /auth/password/reset`) and the updated Player Zero response shape, with typed response bodies for each.

## 1.1.0

### Minor Changes

- 69b1331: Define cursor pagination as the canonical list shape (`{ data, pagination: { nextCursor, hasMore, limit } }`) and add `CursorPagination`, `CursorPaginationSchema`, and `CursorQuerySchema` types. The SDK's new `paginateCursor()`/`collectCursor()` helpers walk the envelope. Server-side, `apps/api/src/common/pagination/cursor.ts` provides a TypeORM helper that applies keyset filters and emits the canonical envelope — adopt it on new list endpoints. Existing offset-paginated endpoints are unchanged; consumers can mix both pagination styles.
- 69b1331: Add `client.auth.whoami()` for validating an API key without performing a
  side-effectful call. Returns the customer id + name, plan, capability list
  entitled by that plan, the authenticating credential's id/label/type/scopes,
  and the auth method (`api_key` vs `dashboard_jwt`). Useful for onboarding
  CI checks and `--health` style scripts where the integrator wants to prove
  "my key is wired and has the scopes I think it does" before shipping.
- 0984fa4: Add the `brag` resource for the F2.7 Brag Button — `client.brag.recordTelemetry()` records a share funnel step and `client.brag.sendSlackPost()` dispatches a Win-State brag to a tenant Slack/Teams webhook. Both map to explicit per-event user actions in the widget consent modal; there is no auto-share path.
- 4d7c563: Add the Symbolic Cause Counter admin resource (F2.12). The tenant-admin `causes` resource backs the Planner "Humanity Hero — Cause Counter" drawer: list/create/update/delete cause definitions and run `preview30Days` to project symbolic units from the last 30 days of eligible events. New exported types `AdminCause`, `CreateCauseParams`, `UpdateCauseParams`, `CausePreview` and `CauseTriggerEvent`.
- dbe33a0: Add the `eventBadges` resource for managing Event-Triggered Badge campaigns. A campaign binds a badge to a time window so any buddy active inside the window earns it once — the surprise, story-bound "you were here that day" badge. Supports list (with grant counts), create, update and delete from the tenant admin surface.
- 0984fa4: Add a `feed` resource for the SeeSaw Bump team feed (F2.11). `client.feed.teamEvents.list({ cursor, limit })` returns the buddy's cursor-paginated team feed of auto-generated events, and `client.feed.teamEvents.clap(id)` toggles the idempotent 👏 clap reaction (a repeat call unclaps). New exported types `FeedResource`, `TeamEventsResource`, `TeamEvent`, `TeamEventKind`, `TeamEventsPage`, `ListTeamEventsParams`, and `ClapResult`.
- 38a4d0e: Add the FlashSalesResource for managing marketplace flash sales (F3.9 Marketplace FOMO). Tenants can list, schedule and cancel flash sales via client.flashSales — a scheduled sale applies a temporary discount to a set of marketplace items for a bounded window, run by a once-a-minute API cron.
- 4d7c563: Add the Founding Cohort admin resource (F2.13). The tenant-admin `foundingCohort` resource backs the Planner "Founding Cohort" drawer: `preview` projects how many buddies the current eligibility config would mark, `backfill` runs the one-shot retroactive assignment, and `listAudit` reads the assignment history. New exported types `FoundingCohortMode`, `FoundingCohortPreview`, `FoundingCohortBackfillResult` and `FoundingCohortAuditEntry`.
- 0984fa4: Add Group Quest resources (F2.5). The widget-token `groupQuests` resource lets an embedded buddy list active quests (`groupQuests.list()`), join one (`groupQuests.join(id)`) and leave one (`groupQuests.leave(id)`); join is idempotent and returns `already_joined`. The tenant-admin `adminGroupQuests` resource backs the Planner drawer with CRUD, the `publish` transition and a manual `forceResolve` watchdog override. New exported types `ActiveGroupQuest`, `JoinGroupQuestResult`, `LeaveGroupQuestResult`, `AdminGroupQuest`, `GroupQuestStatus`, `GroupQuestRewardConfig`, `CreateGroupQuestParams`, `UpdateGroupQuestParams` and `ForceResolveResult`.
- 9933e8f: Hexad survey resource + widget-token auth mode.

  **New `widgetToken` auth mode.** `HatchedClient` now accepts a third config shape — `{ widgetToken }` — alongside the existing `apiKey` (server) and `publishableKey` (browser) modes. Widget embed/session tokens are buddy-scoped and browser-safe; the client auto-allows them on `/widget/*` runtime paths and refuses everything else.
  - New export: `WidgetTokenConfig` config type.
  - New export: `WidgetTokenScopeError` — thrown without a network round-trip when a widget-token client calls a non-widget endpoint or a mutation that hasn't opted in via `allowWidgetToken`.
  - The runtime guard recognises widget tokens as browser-safe, so the browser-emit warning only fires for secret keys.

  **New `hexadSurvey` resource** for the Marczewski Hexad self-assessment widget. All four methods are widget-token scoped — admin recompute and aggregate endpoints stay dashboard-only.
  - `hexadSurvey.questions()` — fetch the 24-question axis catalog plus the current consent version.
  - `hexadSurvey.submit({ answers, consentVersion })` — UPSERT the widget user's Likert responses; returns the normalised six-axis distribution, primary type, and retention expiry. `consentVersion` is required; submitting without acknowledging the active version is rejected server-side.
  - `hexadSurvey.me()` — read back the user's stored response (or `null` when none).
  - `hexadSurvey.deleteMine()` — withdraw consent and purge the raw answers.

- bc02f0d: Add the `kudos` resource for F2.3 peer recognition: `client.kudos.send()` posts a buddy→buddy kudos, `client.kudos.received()` and `client.kudos.given()` read the recent kudos feed and lifetime assist count.
- bc02f0d: Add team scope to the leaderboard resource. leaderboard.get(\{ scope: 'team' }) now restricts the board to the requesting buddy's active team roster (HTCH-48), alongside the existing global scope. scope: 'friends' throws a client-side not-implemented error until Phase 3 ships. The response gains an optional effectiveTeamId field, and the scope-policy pill label is exposed as teamLabel.
- 8e95453: Add the LeaguesResource for the F4.1 LEAGUES endgame feature. `client.leagues.me()` reads the widget buddy's live league standing — current tier, the next-tier promotion target, cohort standings, season countdown and the demotion-zone flag. The snapshot self-gates: an `available: false` result (plan not entitled, no active season, or buddy not enrolled) is a normal outcome callers render nothing for, never an error.
- dbe33a0: Add the LotteryResource for managing recurring lotteries (F3.11 Rolling Reward). Tenants can list and CRUD lottery definitions via client.lottery, read past-draw history, fetch the live "next draw" preview and run a non-persisted draw simulation. A lottery silently enters any buddy that meets its eligibility rule into a weekly or monthly draw, resolved by a once-a-minute API cron.
- bbc4600: Expose widget marketplace and outfit composition helpers, aura tier fields, and the new marketplace/aura webhook event types.
- 0984fa4: Add `client.marketplace.gift()` for F2.4 Social Treasure gifting: a buddy can gift a marketplace item to a teammate, the sender pays and the item lands in the recipient's inventory. Marketplace items now expose `isGiftOnly` for items that can only be received as a gift.
- 0984fa4: Add a `mentor` resource for widget consumers (F2.6 Mentorship, visibility-only). `client.mentor.setAvailability({ available })` toggles the buddy's mentor availability, `client.mentor.teamMentors(teamId)` lists a team's available mentors with server-rendered contact deep links, and `client.mentor.logSession({ hours, menteeLabel, summary })` / `client.mentor.sessionsForMe()` cover the self-reported Mentor Hours log. New exported types `MentorDirectoryEntry`, `MentorSession`, `MentorSessionsResponse`, `LogSessionParams`, and `LogSessionResult`.
- 6618ae8: Add `nextBestAction` resource that returns the single highest-priority next-best-action for the widget session's buddy. The server orchestrates eight strategies (streak warning, almost-badge, dress-first-outfit, etc.), applies tenant overrides, and caches per buddy for 30 seconds — clients receive `{ action, fallbackUsed }`. Buddy responses now also expose `isNaked` to signal the Build-From-Scratch onboarding flow.
- 06d98b6: Add `client.buddies.prestige()` and `client.buddies.prestigeStatus()` for the F4.3 Prestige Loop. A buddy that has reached the maximum evolution stage can prestige — reset to stage 0 in exchange for an incremented prestige level and a permanent prestige aura. `prestigeStatus()` reports whether the buddy can prestige and, when it cannot, the blocking reason (not at max stage, cooldown active, champion required, or the loop disabled for the workspace); `prestige()` performs the reset and returns the new prestige level, aura tint and reverted image.
- dbe33a0: Add the `profileTemplates` resource for the F3.14 Profile Page Editor v2. Tenant admins can now list the template gallery (built-in system templates plus their own), create, update and delete custom profile-page templates, and bulk-apply a template to all buddies or a single audience.
- 69b1331: `allowBrowser: true` is now refused at runtime unless the SDK can confirm it's being executed inside a test runner. Recognized markers are `NODE_ENV=test`, `VITEST`/`JEST_WORKER_ID` env vars, or the presence of `globalThis.expect` + `globalThis.it`. Outside a test runner, attempting to bypass the secret-key-in-browser guard throws immediately. This closes the "I copy-pasted my unit test config into production" footgun without changing any test-time behavior.
- 69b1331: Export `ErrorCode` — a typed enum-shaped object whose keys map every known Hatched API error code (e.g. `ErrorCode.EventQuotaExceeded === 'event_quota_exceeded'`) — and `KnownErrorCode`, the union of those string values. Compare `HatchedError#code` against `ErrorCode.*` instead of typing string literals. Unknown codes still arrive on `err.code` as plain strings; the enum covers what the current SDK release knows about.
- a95c14e: Align typed error, webhook, and operation surfaces with the live API.
  - **WebhookEvent now covers every server-emitted event (86, up from 26).** Handlers can exhaustively switch on the full set — kudos, group quests, leagues and the league-season lifecycle, council, mystery box, lottery, showroom, cause-counter, mentor, and lapsed-user re-engagement events are all typed instead of falling through as plain strings.
  - **ErrorCode gained the remaining server codes** (`bad_request`, `capability_disabled`, `onboarding_cap_reached`, `not_found`, `bad_gateway`, `service_unavailable`, `onboarding_extract_failed`, `internal_server_error`) so `err.code` comparisons stay typed across the whole surface.
  - **OperationStatus gained `cancelled`, and `operations.wait()` treats it as terminal.** A cancelled async operation previously polled until the timeout threw; it now returns as soon as the status is reached.
  - **`InsufficientBalanceError` reads `balance` / `required` from `err.details`** to match the wire shape (the API moved them under `details`); `err.balance` and `err.required` are populated again. It is now also raised for the `402` marketplace-gift path (a payment short on coins), not just the `400` spend path, and `err.statusCode` reflects whichever the API returned.
  - **A bare HTTP 400 now parses to a `HatchedError` that keeps its 400 status and code** (e.g. `bad_request`). Request-body/DTO validation moved server-side to `422 validation_failed` (surfaced as `ValidationError`), so a 400 is no longer mis-reported as a 422.

- 69b1331: Add pluggable `SdkLogger` interface and `logger` config option. Pass a Pino/Winston/Bunyan instance — or any object with optional `debug`/`info`/`warn`/`error` methods — to replace the SDK's default `console.warn` output. Warn-level entries (literal-secret detection, retry hints) emit regardless of `debug`; debug-level request/response traces still gate on `debug: true`. Also exports a `consoleLogger()` factory for the default behavior.
- 69b1331: Ship auto-generated OpenAPI types alongside the hand-written resources. `import type { paths, components, operations } from '@hatched/sdk-js'` exposes every public endpoint's exact request/response shape as derived from `apps/api/openapi.public.json`. The contract is regenerated via `pnpm --filter @hatched/sdk-js generate:types` and frozen in CI by `check:types`, so the SDK's typings can never drift from the API specification. Use the generated `paths['/buddies/{id}']['get']['responses']['200']['content']['application/json']` style references when you need a shape that doesn't have a hand-written equivalent yet.
- 69b1331: Add async-iterator pagination helpers. `paginate()` + `collect()` walk offset-paginated endpoints (`{ data, meta: { total, page, limit } }`), while `paginateCursor()` + `collectCursor()` walk cursor-paginated endpoints (`{ data, pagination: { nextCursor, hasMore, limit } }`) — Hatched's canonical pagination shape for new list endpoints. All four return an `AsyncIterableIterator<T>` that stops automatically when the consumer breaks or the page chain ends, and accept `maxPages` + `AbortSignal` for runaway protection. Reduces boilerplate when iterating over `buddies.list()`, `operations.list()`, and any new cursor-based endpoint.
- 69b1331: Expose retry metadata via `hatched.getLastRetryMetadata()`. Returns `{ attempts, reasons, totalDelayMs, totalElapsedMs }` for the most recent request, where `reasons` is a per-retry list of `'429' | '5xx' | '408' | 'network'`. Lets consumers feed observability dashboards with how often the SDK had to retry to land a call, and inflate timeouts on hot paths where retries are routine. The new `RetryMetadata` type is exported alongside the existing `RateLimitSnapshot`.
- ce6ff37: Fix webhook signature verification and the `whoami` response types.
  - **Webhook verification now actually works.** `WebhooksResource.verifySignature` and every framework adapter (`verifyExpressRequest`, `verifyFastifyRequest`, `verifyHonoRequest`, `verifyNextAppRequest`, `verifyNextPagesRequest`) previously parsed a `t=…,v1=…` header that the API never sends — so no real delivery could be verified. They now match the wire contract: `X-Hatched-Signature: sha256=<hex>` plus a separate `X-Hatched-Timestamp` header. Adapters read both headers automatically; the raw `verifySignature` accepts the timestamp via `options.timestamp`. The legacy combined format is still accepted for safety.
  - **`whoami()` types match the wire.** `WhoamiResult` and `ApiKeySummary` were declared snake_case (`customer_id`, `key_type`, …) but the client camelCases every response, so those fields were `undefined` at runtime. They are now camelCase (`customerId`, `keyType`, `lastUsedAt`, …).
  - **`leaderboard.get` no longer offers a throwing `scope: 'friends'`** — the unimplemented option was removed from the public type instead of throwing at runtime.
  - **User-Agent reports the real version.** The `SDK_VERSION` constant is now injected from `package.json` at build time, fixing a drift where it reported an older version.

- bc02f0d: Add a `teams` resource for widget consumers — `client.teams.me()` returns the buddy's team, role and members, and `client.teams.leave(teamId)` leaves a team. New exported types `Team`, `TeamMember`, `TeamRole`, and `MyTeamResponse`.
- 69b1331: Add framework adapters for verifying inbound webhook deliveries:
  `verifyExpressRequest`, `verifyFastifyRequest`, `verifyHonoRequest`,
  `verifyNextAppRequest`, `verifyNextPagesRequest`. Each adapter pulls the
  raw body + signature header from its framework's request shape and
  delegates to `WebhooksResource.verifySignature`, returning a uniform
  `{ valid, event, reason }` result. Both the package root and
  `@hatched/sdk-js/webhooks` deep import expose the adapters.
- 69b1331: `WebhooksResource.verifySignature` and every framework adapter
  (`verifyExpressRequest`, `verifyFastifyRequest`, `verifyHonoRequest`,
  `verifyNextAppRequest`, `verifyNextPagesRequest`) now accept either a
  single secret or an array of secrets. Pass `[currentSecret, previousSecret]`
  during a rotation window to accept payloads signed under either secret
  without a separate code path. Adds `client.webhooks.rotateSecret(endpointId)`
  to call `POST /webhook-configs/:id/rotate-secret` and return the new
  plaintext secret.
- ce6ff37: Add five widget-token resources so embedded buddy widgets can drive their in-app surfaces with full types: `client.notifications` (feed list, unread count, mark read / dismiss / dismiss-all / snooze), `client.mysteryBox` (state + claim), `client.council` (list my narrative proposals + submit a proposal), `client.freeLunch` (pending welcome notification + acknowledge), and `client.beginnersLuck` (hatch-ceremony result). Each wraps the corresponding `/widget/*` endpoint with hand-defined response types matching the API JSON. A new coverage test ratchets the SDK against the public spec's `/widget/*` surface so future widget endpoints can't silently ship without either an SDK resource or an explicit acknowledgement.
- cf3f560: Add the `players` resource and sync webhook + response contracts with the server.
  - New `players.zero()` (idempotent create-or-get of the workspace demo player, user id `player-0`) and `players.zeroStatus()` (read-only existence/hatch check) — previously this endpoint was only reachable by hand-rolled HTTP.
  - `WebhookEvent` union now matches the server catalog exactly: adds `buddy.ceremony_completed`, `free_lunch.granted`/`free_lunch.seen`/`free_lunch.dismissed`, `group_quest.joined`/`group_quest.left`, `outfit.saved`/`outfit.worn`/`outfit.deleted`, `gate.unlocked` and `skill.decayed`; removes `marketplace.next_drop_announced` and `self_awareness.profile_revealed`, which were documented but never emitted.
  - Delete acknowledgements are now typed against the standard `{ ok: true }` success envelope instead of per-endpoint `{ deleted: true }` shapes.

### Patch Changes

- 0eb7ba0: Add typed widget `badges.list({ includeLocked })` and `leaderboard.get(...)` SDK resources for the badge collection grid and leaderboard view-mode APIs.
- 06d98b6: Add `client.leagues.bossFightProgress()` for the F4.2 Boss Fight seasonal challenge. It returns the widget buddy's progress toward the season-long target — the name and description, the target metric and value, the buddy's own progress and completion rank, the deadline, and the challenge leaderboard. An unavailable view is a normal outcome (no active boss fight) and callers render nothing.
- 8e95453: Add `client.leagues.seasonHighlights(seasonId)` for the F4.1a Season Closing Ceremony. It returns a buddy's four personalized season highlights (best week, kudos sent, items collected, cohort role), each z-scored against the cohort so callers can auto-pick the most brag-worthy moment, plus the season outcome, final rank and public-share code.
- 06d98b6: Add `isMentor` to the `Buddy` type — true when a buddy holds the mentor role (F4.4 Mentor Role tier). Also fixes `buddies.prestigeStatus()` / `buddies.prestige()` to read the camelCased response correctly, so their fields are no longer undefined at runtime.
- 1cf026d: Add `ErrorCode.MissingAudience` (`missing_audience`) and `ErrorCode.UnknownAudience` (`unknown_audience`) to the known error-code enum. The API already returns these 400s when a request to a multi-audience customer omits or mis-names the `audience` field; the SDK now recognises them in `KnownErrorCode` so callers can switch on them exhaustively.
- 569de43: SDK README header gains a one-line tagline ("The motivation API your backend already speaks.") above the existing intro paragraph, and the intro picks up a single-sentence bridge to the Yu-kai Chou Octalysis framework — the data model the SDK was shaped around. The npm `description` field is revised to lead with the runtime + language + product + framework hook + security default in one line ("TypeScript-first Node SDK for Hatched — the B2B gamification platform with Octalysis-native scoring. Server-only; secret keys never in the browser."). No runtime change; package metadata + README markup only.
- 0984fa4: Add a `socialNorms` resource for widget consumers (F2.9). `client.socialNorms.today()` returns the buddy's positive-framing team norms for today — completion, momentum, and elitism framings, server-rendered and believability-gated. New exported types `SocialNorm`, `SocialNormFraming`, and `SocialNormsTodayResponse`.

## 1.0.0

### Major Changes

- 6dff237: PathDisplayMode renamed: replace `'path'` with `'straight'` (clean centered column) and add `'zigzag'` (Duolingo-style alternating sides). Existing `'path'` consumers must migrate to `'straight'`. The legacy half-zigzag rendering (node offset, text static) is removed.

### Minor Changes

- d73cafe: First-run bootstrap ergonomics: `eggs.create({ userId, ensure: true })` now reuses the user's existing waiting/ready egg instead of creating a new one (avoids the per-user active-egg cap on retries). `Egg` responses include `buddyId` (non-null once the egg is hatched). Two new typed 409 errors are surfaced: `NoPublishedConfigError` (raised by `eggs.create` before a config version is published — exposes `publishUrl`) and `ActiveEggLimitError` (raised when the active-egg cap is hit — exposes `max` and `active[]` with the existing egg ids/statuses).

## 0.5.0

### Minor Changes

- d758872: Add `paths` resource — guided multi-step journeys (Duolingo-style). New
  `client.paths` exposes admin CRUD over path definitions, steps, and
  sub-steps; `setActive()` flips the audience's single active path
  atomically; `getForBuddy()` returns the buddy-scoped runtime payload
  with locked / available / completed sub-step states; `completeSubStep()`
  manually marks a sub-step done and returns cascade flags so callers can
  celebrate without an extra round-trip. Sub-steps support an optional
  `contentUrl` + `ctaLabel` for deep-linking into a customer's LMS.

  `events.send()` response now also exposes `streakUpdates` and
  `pathUpdates` on the returned `EventEffects`, so a single track call can
  drive streak counters and path widgets in addition to coins/badges
  without an extra fetch.

- 42907d4: Added new feature: path and performance improvements

## 0.4.4

### Patch Changes

- ea76743: Republish 0.4.3 with corrected package metadata: `repository.url` and `bugs.url` now point to the public SDK repo at `github.com/hatched-live/hatched-sdk-js`. (0.4.3 was never published to npm.)

## 0.4.3

### Patch

- **Buddy appearance types** — `Buddy` now exposes `baseImageUrl`, typed equipped item objects, and the `appearance` status block used by the persistent AI compositing pipeline.
- **Equip result typing** — `buddies.equip()` now returns a typed `{ accepted, operationId, status, appearanceStatus, cached }` result so clients can distinguish instant cache hits from queued appearance generation.
- **Rerender appearance** — new `buddies.rerenderAppearance(buddyId)` method backed by `POST /buddies/:id/appearance/rerender`. Use when `appearance.status === 'failed'` (especially `error.code === 'needs_rerender'` after the appearance pipeline migration) to regenerate the bare stage image. Equipped items are cleared from the rendered set; re-equip after status returns to `ready`.

### Behavior change (non-breaking type-wise, breaking semantically)

- **Evolution no longer blocks on item composite failure.** Previously, if the
  bare stage image succeeded but compositing equipped items failed, the buddy
  stayed on the prior stage and the evolve operation was marked failed. Now the
  buddy advances to the new stage with its bare base image and `appearance`
  reports `failed` or `awaiting_credits`. The evolve operation completes
  successfully and the appearance recovery flow re-attempts the composite.
- **Awaiting-credits self-recovery.** When equip/evolve hits a credit limit, an
  internal scheduler retries with exponential backoff (60s → 5m → 15m → 30m).
  Clients should poll `buddy.appearance` rather than the original operation
  status to track final resolution.

## 0.4.1

### Patch

- **Docs domain** — all references now point to `docs.hatched.live` (single canonical domain). The legacy `docs.hatched.com` host is retired; error messages, README links, and `package.json.homepage` have been updated. No API or behavior changes.

## 0.4.0

### Minor

- **Publishable-key hardening** — browser keys can mint scoped read-only embed tokens, but no longer mint interactive widget sessions. Interactive session tokens stay server-only.
- **Webhooks resource alignment** — SDK methods now target the production `/webhook-configs` API and unwrap dashboard response envelopes.
- **Docs/examples refresh** — README snippets, widget examples, and auth guidance now match the current CDN loader and `Authorization: Bearer` API model.

## 0.3.0

### Major

- **Two-tier token model**. Tokens now have an explicit `kind`: `primary` (spendable via `buddies.spend`, marketplace purchases, gate unlocks) or `progression` (earn-only, feeds evolution readiness). `token_config` DTOs unlocked from the legacy 4-tuple (`hatch_token`/`evolution_token`/`reroll_token`/`gift_token`) — customers now pick their own `token_key` (e.g. `gems`, `mana`, `xp`). Spending a progression token returns `progression_not_spendable`.
- **Canonical item categories**. The two coexisting taxonomies (`hat`/`held_item`/… vs `headwear`/`eyewear`/…) collapse into 8 canonical slots: `background`, `body`, `feet`, `hand`, `neck`, `face`, `head`, `accessory`. Migration 023 normalises existing rows and locks the column via CHECK.

### Minor

- **`buddies.tokens(buddyId)`** — typed primary + progression balance snapshot with lifetime earn/spend sums.
- **`buddies.evolutions(buddyId)`** — paginated stage-transition timeline (prod + demo). Backed by a new `buddy_evolutions` table that captures every evolve with its image and trigger event.
- **`GatesResource`** — new `hatched.gates.unlock(buddyId, gateKey)` / `unlocks(buddyId)` primitive. Customers author gates in the dashboard (`gate_key`, `token_key`, `cost`, `metadata`); end-users spend tokens to unlock features. Unlock is idempotent — repeat calls return `alreadyUnlocked: true` without touching the economy.
- **Equip safety rails** — `TooManyItemsError` (max 4 equipped) and `CategoryConflictError` (two non-accessory items in the same category) surface at the SDK layer with `details` carrying the specifics.
- **Stage-aware item artwork** — `items.stage_image_urls` jsonb lets designers ship stage-2-specific hats; the composite pipeline picks the right variant per stage.
- **Evolve×equip pipeline (pre-0.4.3)** — the initial implementation attempted to block `operations.wait(evolveOp)` until equipped items were composited. In 0.4.3 this was replaced by `buddy.appearance` status recovery so stage advancement can complete even when item compositing is delayed.
- **Theme-aware defaults** — empty marketplace or token bundle at onboarding seeds from a theme-appropriate catalog (fantasy → gems/mana + fantasy items, fitness → reps/streaks, etc.). Source tracked in `customers.settings.applied_sources`.

## 0.2.1

### Patch

- **Docs**: README now has a dedicated "Two ways to authenticate" section with a secret-vs-publishable key comparison, a browser-safe publishable-key example, and a per-resource secret/publishable capability matrix. No code changes.

## 0.2.0

### Major

- **camelCase public surface** — all params and response fields exposed by the SDK are now camelCase. Snake_case wire format is converted transparently. Migration: rename `user_id` → `userId`, `event_id` → `eventId`, `occurred_at` → `occurredAt`, etc. The same applies to response fields (`egg.egg_id` → `egg.eggId`, `op.operation_id` → `op.operationId`).
- **Operation.wait** — `operations.waitForCompletion` has been replaced with the shorter `operations.wait`. The old name is still exported as a deprecated alias.

### Minor

- **Server-only runtime guard** — the SDK now throws when constructed in a browser-like environment with a secret key. Override with `allowBrowser: true` (test-only).
- **Publishable key support** — browser-safe `HatchedClient({ publishableKey })` constructor variant. Only read endpoints and `embedTokens.create` are allowed; mutation methods return `PublishableKeyScopeError` at runtime.
- **Automatic retries** — exponential backoff + jitter on network errors, 408, 429 (retry-after honoured), and 5xx. Configurable via `maxRetries` (default 3).
- **AbortSignal on every method** — pass `signal` to cancel in-flight requests; combined with the internal timeout via `AbortSignal.any`.
- **Request id tracking** — `hatched.getLastRequestId()` exposes the `X-Request-Id` of the most recent response. SDK-generated request ids are sent on every call.
- **Webhooks resource** — `hatched.webhooks.list/create/delete/deliveries/replay` + `WebhooksResource.verifySignature(rawBody, header, secret)` for `Hatched-Signature` verification.
- **New error classes** — `AuthError` (base for 401/403), `PublishableKeyScopeError`, `ConfigVersionMismatchError`, and a `ResourceNotFoundError` alias for `NotFoundError`.
- **tsup dual build** — `dist/index.mjs`, `dist/index.cjs`, plus `.d.ts`/`.d.cts`. Subpath exports for tree-shaking: `@hatched/sdk-js/errors`, `@hatched/sdk-js/webhooks`.
- **`sideEffects: false`** — enables aggressive tree-shaking by bundlers.
- **`timeoutMs` alias** — equivalent to `timeout`, aligns with the docs.
- **`fetch` override** — supply a custom `fetch` implementation for edge runtimes and tests.

### Fixes

- Correct URL concatenation — paths now preserve the base `/api/v1` prefix (previously absolute paths could drop it).

## 0.1.1

Initial private-preview release.
