# Changelog (/docs/api/operational/changelog)



Every shipped change lands here on the day it ships. Grouped by the standard [Keep a Changelog](https://keepachangelog.com/) categories: **Added** / **Changed** / **Deprecated** / **Removed** / **Fixed**. Dates are UTC.

Subscribe by RSS or watch the repo - your call.

## Planned [#planned]

Scheduled for the next release. Shape may change before launch.

### Planned - Added [#planned---added]

* Webhooks: signed POST delivery, HMAC-SHA256 signatures, retry policy, subscription registration. See [Webhooks](/docs/api/operational/webhooks) for the committed shape.
* Ads CRUD on Meta, TikTok, Apple (`POST/PATCH /v1/projects/:id/ads/{campaigns,adsets,ads}`).
* Approval v2: `trust_threshold` + `current_trust_score` for auto-transition to continuous approval.
* (none — see Changed below for engagement template v2)
* Leased-account request-flow visibility: richer status vocab (`requested` → `in_review` → `provisioning` → `assigned` → `failed`), `webhook: lease_request.assigned`.
* SSE streaming on `GET /v1/jobs/:jobId/events` (optional - polling keeps working).
* Recommendations taxonomy: `new_hook`, `cadence`, `audience_gap`, `ads_budget`.
* Per-project credit CSV export for partner rebilling.

## 2026-06-09 — Sub-org wallets: allocate returns a transfer; metadata tightened [#2026-06-09--sub-org-wallets-allocate-returns-a-transfer-metadata-tightened]

### Changed [#changed]

* **`POST /v1/organizations/:orgId/credits/allocate`** now returns the **created transfer resource** instead of a bare balance snapshot: a stable `id` (`txn_<uuid>`, the parent-side ledger row — reconcile against it and re-fetch via [`GET …/credits/events`](/docs/api/reference/organizations/credits/list-events)), `created`, the echoed `description`/`metadata`, the child's `balance`/`available`, and `replayed`. An idempotent replay returns the **same** `id` + `allocated` with `replayed: true`.
* Partner &#x2A;*`metadata`** is now **Stripe-shaped** across org create/patch and credit allocation: string→string, key ≤ 40 chars, value ≤ 500 chars, ≤ 50 keys (16 KB backstop). Out-of-bounds → `422 VALIDATION`.

### Fixed [#fixed]

* **`PATCH /v1/organizations/:orgId`** metadata **merges** key-by-key (Stripe-style) again — send only the keys you're changing; unset one with an empty string (`""`); `metadata: null` clears all. (Per-key `null` is retired; use `""`.)

## 2026-06-03 — Sub-organizations [#2026-06-03--sub-organizations]

Run customers as isolated [child organizations](/docs/api/concepts/organizations) under your parent org, the Stripe Connect / Twilio Subaccounts shape. Additive and non-breaking — the flat one-project-per-customer model is unchanged. See the walkthrough guide.

### Added [#added]

* **Organization control plane.** New `org:admin`-gated endpoints to create, read, list, patch, [suspend](/docs/api/reference/organizations/suspend-organization), [resume](/docs/api/reference/organizations/resume-organization), and [archive](/docs/api/reference/organizations/archive-organization) child orgs (`/v1/organizations`, `/v1/organizations/:orgId`, `…/suspend`, `…/resume`). Each child is an isolation boundary with its own wallet, audit log, projects, and keys. Hierarchy is one level deep. New scope: `org:admin` — **deny-by-default** (must be granted explicitly; legacy unscoped and partner-tier keys do **not** get it for free).
* **Act on behalf of a child.** The `X-Layers-Organization` header lets an `org:admin` parent key run any existing endpoint inside a direct child. See [Authentication](/docs/api/getting-started/authentication#acting-on-behalf-of-a-child-org).
* **Child API keys.** Mint, [list](/docs/api/reference/organizations/api-keys/list), [rotate](/docs/api/reference/organizations/api-keys/rotate), and [revoke](/docs/api/reference/organizations/api-keys/delete) least-privilege keys bound to a single child (`/v1/organizations/:orgId/api-keys`). Scopes must be a subset of the parent's; `org:admin` is never delegable to a child. The secret is shown once on mint/rotate. Rotation is zero-downtime — the old secret keeps working for a 24-hour grace window, then stops. `/v1/whoami` now returns `parentOrganizationId`, and `apiKeyId` is emitted in the `key_`-prefixed form.
* **Per-child wallets + allocation.** Fund a child from the parent wallet with [`POST /v1/organizations/:orgId/credits/allocate`](/docs/api/reference/organizations/credits/allocate) (idempotent). Read a child's wallet with [`GET …/credits`](/docs/api/reference/organizations/credits/get-credits) and ledger with [`GET …/credits/events`](/docs/api/reference/organizations/credits/list-events). Archiving a child reclaims its unspent (non-reserved) credits to the parent (`reclaimedCredits` on the archive response).
* **Per-child spend caps + auto-refill, set via the API.** Read and set a child's monthly cap and auto-refill rule with [`GET` / `PATCH /v1/organizations/:orgId/credit-config`](/docs/api/reference/organizations/credit-config) — `monthlyCreditCap`, `refillThreshold`, `refillAmount` (all in credits, nullable) plus the derived `autoRefillEnabled`. A capped child's over-cap generation is denied with `402 BILLING_EXHAUSTED` (`details.reason: "cap"`); auto-refill tops a child up from the parent when its `available` drops below the threshold (a capped child is never refilled past its ceiling). Auto-refill is both-or-neither (`422 REFILL_REQUIRES_THRESHOLD_AND_AMOUNT`). The config also appears on the `GET /:orgId` summary as `creditConfig`. See the [Credits concept](/docs/api/concepts/credits).
* **Webhook firehose (`scope: "all_children"`).** A parent `org:admin` key can register one [webhook endpoint](/docs/api/reference/webhooks/create-endpoint) that receives every direct child's events alongside its own, each attributable via `data.organizationId`. Defaults to `own`; setting `all_children` requires `org:admin`.
* **Migrate flat projects into children.** [`POST /v1/organizations/migrate`](/docs/api/reference/organizations/migrate) atomically creates child orgs and moves existing projects under them from a `{ projectId: childOrgName }` mapping. Historical credit events stay on the parent; migrated projects keep a 30-day parent read-grace.

### Changed [#changed-1]

* **`available` + `reservedCredits` on the credits wallet.** [`GET /v1/credits`](/docs/api/reference/credits/get-credits) now returns `available` (gross `balance` net of credits reserved for in-flight generations) and `reservedCredits` alongside the existing fields. Gate generate decisions on `available`. Purely additive — existing fields are unchanged.
* **New `allocation` event type on your own ledger.** [`GET /v1/credits/events`](/docs/api/reference/credits/list-events) can now return events with `eventType: "allocation"` — the parent side of a parent→child transfer (negative when you fund a child, positive when an archived child's unspent credits are reclaimed). `metadata.direction` is `allocate` or `reclaim`, `metadata.counterpartyOrgId` (`org_`-prefixed) names the child, and `metadata.transferId` (`txn_`-prefixed) joins to the allocate response `id`. &#x2A;*If your integration enumerates or switches on `eventType`, add a branch for `allocation`** so transfers aren't dropped from your reconciliation.
* **`data.organizationId` on every webhook payload.** Webhook delivery envelopes now include `data.organizationId` (the prefixed id of the org the event belongs to) on all events, not just firehose ones. Purely additive — existing `data.*` consumers are unaffected; it's what makes the `all_children` firehose attributable.

## 2026-05-08 — API integration redesign (the "first design partner" round) [#2026-05-08--api-integration-redesign-the-first-design-partner-round]

End-to-end redesign in response to the first design-and-build partner's wishlist. The cross-cutting principle is **docs are the contract** — every change here lands in code at the same time the docs claim it.

### Added [#added-1]

* **Preview object on every content surface.** Every container response now carries a normalized [`preview`](/docs/api/concepts/preview-object) object with `kind`, `primaryUrl`, `thumbnailUrl`, `imageUrls`, `videoUrl`, `hlsUrl`, `durationMs`, `aspectRatio`. Same shape on get / list / job-completion / `content.generated` webhook. Backed by new `content_containers` columns + ffprobe + poster-frame extraction activities.
* **Multi-variant fan-out.** `variantCount` on [`POST /v1/projects/:id/content`](/docs/api/reference/content/slideshow-builder) accepts 1–5. Response is `containerIds[]` plural; billing scales linearly.
* **Hook is the only call-time creative input.** [`POST /v1/projects/:id/content`](/docs/api/reference/content/slideshow-builder) takes a top-level required `hook` string, used verbatim in slide 1 / overlay #1. Brand voice, language, audience, and influencer roster are drawn from project + layer state via `PATCH /v1/projects/:id` and the layer / influencer endpoints — no `brief` object at the partner surface. Pull a vetted hook from the new [`GET /v1/projects/:id/content/hooks`](/docs/api/reference/content/list-hooks) endpoint or send your own.
* **Partner asset upload.** New [app-media endpoints](/docs/api/concepts/media-library) — `POST /v1/projects/:id/app-media` (upload from a public URL), `GET …`, `DELETE …/:mediaId` — with per-kind mime + byte-cap validation for `logo` / `screenshot` / `demo-video`. URL-based fetch through the SSRF guard; no presign / finalize dance at the partner edge. Scope: `projects:write`. Ungates `ugc-remix` partner-callability.
* **Ads write surface.** Full mirror of admin `paid-media/*` routes under `/v1/projects/:id/ads/...`. New scopes: `ads:write:campaigns`, `:budgets`, `:creative`, `:lifecycle`, `:policy`, `:optimizer_trigger`, `:pending`, `:capi` (existing `ads:write:connect`, `ads:write:override` retained). Headline simplified create-campaign endpoint (budget + outcome + management mode + creative mode). Cents universally on every money field. Apple campaign-create lifted (see LOCK 10 investigation). 15 webhook events under `ads.*` and `approval.*`.
* **TikTok admin parity.** `paid-media/tiktok/{authority,defaults,kill-switch,pending,creatives}` admin routes shipped to match Meta + Apple. Required for symmetric partner write surface.
* **SDK platform support.** `nextjs`, `vite`, `react-router` added to the SDK app platform enum. New install-spec generator branches with framework-specific `filesToCreate`. New Next.js, Vite, React Router guides.
* **Server-side event forwarding.** New [`POST /v1/events`](/docs/api/reference/events/forward) — partner-API surface for batch event ingest, distinct from the SDK runtime path. New scope: `events:write`.
* **Active tracking probe.** New [`POST …/sdk-apps/:appId/verify-tracking`](/docs/api/reference/sdk-apps/verify-tracking) — sends a synthetic event tagged `test_event_code` and reports per-platform CAPI dispatch confirmation. Companion [SDK health concept page](/docs/api/concepts/sdk-health).
* **CAPI per-app config wiring (CF-3 fix).** [`PATCH /v1/projects/:id/sdk-apps/:appId`](/docs/api/reference/sdk-apps/patch-sdk-app) now writes through to `project_layers.config.capi` (the relay's read path). Previously the partner toggle was disconnected — every CAPI claim in docs is now honest.
* **Audit log for partners.** [`GET /v1/audit-log`](/docs/api/reference/audit-log/list) now returns partner-attributable rows (`actor_type: "partner"`) with the partner's `actor_organization_id` and `actor_api_key_id` populated. Every gate decision logs a `verdictId` returnable on the partner write response. Backed by migration `20260508000000_partner_write_safety.sql` (adds `'partner'` to the `layer_ads_audit.actor_type` CHECK, plus `actor_organization_id` + `actor_api_key_id` on `layer_ads_audit` and `optimizer_pending_actions` with partial indexes).
* **Approval dispatch observability.** New `dispatched_at` + `dispatched_failure_reason` columns on `optimizer_pending_actions` (same migration), wired to the new `approval.dispatched` / `approval.dispatch_failed` webhooks. Partners reading [`GET …/pending`](/docs/api/reference/ads/list-pending) see the platform-side dispatch timing inline.
* **Wildcard scopes.** `hasScope()` (`api/lib/partner/scope.ts`) now expands `<resource>:<action>:*` to cover every sub-scope under that pair — `ads:write:*` covers all eight new sub-scopes. Existing `<resource>:*` and `*` wildcards continue to work. See [API keys → wildcard scopes](/docs/api/concepts/api-keys#wildcard-scopes).
* **Comprehensive ads webhook set.** All 15 events in Phase 1 (vs. the bare-min 6): `ads.account.{connected,disconnected,token_expired}`, `ads.optimizer.{action,run.completed}`, `ads.write.{executed,denied}`, `ads.creative.{flagged,unlocked}`, `ads.policy.violation`, `approval.{dispatched,dispatch_failed,approved,disapproved}`, `ads.budget.cap_exceeded`.
* **First-party React example app.** A `react-quickstart&#x60; walkthrough — full happy path (bootstrap → influencer → generate → poll → gallery → approve → connect → publish → metrics). &#x2A;(Example app docs are being rebuilt; the page is temporarily unavailable.)*

### Changed [#changed-2]

* **Format enum.** Partner enum is now `slideshow-builder | ugc-remix` (v1 launch set). `auto` is **removed** — partners must name a type. `video-remix | slideshow-remix` are reserved for v2 (return `UNSUPPORTED_FORMAT` in v1) — the surface to pass / discover a third-party source post is not yet exposed. See [content items → Roadmap: source-coupled formats](/docs/api/concepts/content-items#roadmap-source-coupled-formats). Naming honesty: `slideshow-builder-remix` is renamed to `slideshow-builder` at the partner surface (the `-remix` suffix was inaccurate; nothing was being remixed).
* **`Idempotency-Key` is the canonical retry mechanism.** Body `id` is rejected with `422 VALIDATION` on every create endpoint (project, sdk-app, influencer, content). The previous "partner-created IDs as alternative" path is gone. See [Idempotency](/docs/api/operational/idempotency).
* **`references` lives at the top level.** Top-level `references = { mediaIds?, assetIds? }` (each capped at 20). Pass [media-library](/docs/api/concepts/media-library) handles via `references.mediaIds[]`.
* **Cents universally for ads.** Every budget / bid / cap field on the partner ads contract is integers in cents. Layers translates per-platform at the proxy boundary (Meta pass-through, TikTok / 100, Apple `{amount: <decimal>, currency: ISO}`).
* **`ads:write` legacy scope.** Mapped to the union of new sub-scopes for backwards compat. New keys should mint specific sub-scopes.
* **Pricing page enum.** `slideshow-builder` 50 credits / `ugc-remix` 120. Multi-variant scales linearly.

### Removed [#removed]

* **`auto` format.** Was non-deterministic plumbing pretending to be a product feature.
* **`refuse_write_without_gate_trg` SQL trigger** (Meta + Apple variants) — previously DEFAULT-OFF backstop; the TS gate + `no-ungated-platform-write` ESLint rule are the only active enforcement. Cleanup in migration `20260507000000_drop_legacy_authority_objects.sql` (PR #1778).
* **"Read-only today" callouts on `reference/ads/list-{campaigns,adsets,ads}.mdx`.** Replaced with bucket-mode authority + sub-scope pointers.

### Fixed [#fixed-1]

* **Misleading 409 message** on duplicate content-create — body `id` is now rejected outright; see Changed above.
* **Apple campaign-create unconditional deny.** Stale Layers product policy (the gate comment claimed Apple's API didn't support it; Apple's API does, and our internal create-agent already uses the endpoint). Lift documented in LOCK 10 investigation.
* **Apple ad-level entity mismatch** — partner endpoints never expose `…/apple/.../ads`; Apple's API has no ad-level entity. Use `…/apple/adgroups/:agid/keywords` for tier-3 (D36).

## 2026-04-24 - Preview release [#2026-04-24---preview-release]

First public release.

### Added [#added-2]

* [`GET /v1/whoami`](/docs/api/reference/organizations/whoami) - resolve an API key to its organization, scopes, and rate-limit tier.
* Projects: create, list, get, patch, archive. `customer_external_id` for sub-tenant scoping.
* GitHub install + ingest: register an installation, list visible repos, kick off `POST /v1/projects/:id/ingest/github` to generate and open an SDK-install PR.
* SDK apps: create, get, patch. CAPI status per app for Meta, TikTok, Apple.
* Deterministic `install-spec` generator for non-GitHub installs (iOS SPM, Android Gradle, web npm, react-native, flutter, expo).
* Event stream query + per-user signal endpoint, project-scoped and PII-redacted by default.
* Conversions rollup and Apple Ads attribution records, project-scoped.
* Influencers: create (async), clone (sync, from image URL or existing), patch, archive, bind reference media.
* Content: generate (async), read, list with filters, signed asset URLs.
* Approval: `POST /v1/content/:id/approve` / `reject`, per-project `requires_approval` + `first_n_posts_blocked` policy, scheduled-posts gate enforcement.
* Social OAuth with partner-owned `returnUrl` (allowlisted); status-poll endpoint; account list, reauth URL, revoke.
* Scheduling + publishing across connected accounts, with approval gate enforced pre-publish.
* Leased TikTok accounts: request endpoint (queued for Layers to fulfill manually), list, release. Provisioning remains a manual admin function - permanently.
* Engagement config read + patch — v1 (`enabled`, `firstComment.{targets,commentTemplate}`, `replyToComments.{targets,autoReplyDelay}`) and v2 (`firstComment.strategy`, `replyToComments.{tone,maxPerPostPerHour,ignoreCommentsMatching,escalateNegativeSentiment}`) wired end-to-end. Former `403 FORBIDDEN_FENCE` is removed.
* Metrics: unified organic metrics, ads metrics, top-performers ranking, scored ads-content list with override PATCH.
* Ads reads: campaigns, ad-sets, ads across Meta, TikTok, Apple. CRUD is planned.
* Unified jobs envelope: [`GET /v1/jobs/:jobId`](/docs/api/reference/jobs/get-job), `POST /v1/jobs/:jobId/cancel`. One pattern covers every long-running operation.
* [Idempotency-Key](/docs/api/operational/idempotency) on every mutating POST, with partner-created resource IDs as a stronger alternative.
* [Rate limits](/docs/api/operational/rate-limits) on `pilot` / `gic_pilot` / `gic_ga` tiers, per-endpoint-class buckets.
* Kill switch - per-key, per-org, and global.
* Stable [error codes](/docs/api/operational/errors) with `requestId` on every response.

### Known gaps [#known-gaps]

* Ads campaign, ad-set, and ad writes - planned. Reads are live.
* Trust-score approval auto-transition - planned. Today you flip `requires_approval` manually or lean on `first_n_posts_blocked`.
* Webhooks - &#x2A;*live.** HMAC-SHA256 signed, 8-attempt retry ladder, dedupe + replay. See [Webhooks](/docs/api/operational/webhooks). Polling remains supported; webhooks are an optimization, not a replacement.
* Defense-in-depth on every publish path - gated at schedule + `scheduled-posts-publish-due` today; IG / TikTok / Managed publish paths get rechecked in a future release.
* LinkedIn + YouTube social platforms - not committed; gated on platform prioritization.
