Changelog
Dated, reverse-chronological. One entry per shipped change. No marketing.
Every shipped change lands here on the day it ships. Grouped by the standard Keep a Changelog categories: Added / Changed / Deprecated / Removed / Fixed. Dates are UTC.
Subscribe by RSS or watch the repo - your call.
Planned
Scheduled for the next release. Shape may change before launch.
Planned - Added
- Webhooks: signed POST delivery, HMAC-SHA256 signatures, retry policy, subscription registration. See 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_scorefor 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
Changed
POST /v1/organizations/:orgId/credits/allocatenow returns the created transfer resource instead of a bare balance snapshot: a stableid(txn_<uuid>, the parent-side ledger row — reconcile against it and re-fetch viaGET …/credits/events),created, the echoeddescription/metadata, the child'sbalance/available, andreplayed. An idempotent replay returns the sameid+allocatedwithreplayed: true.- Partner
metadatais 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
PATCH /v1/organizations/:orgIdmetadata merges key-by-key (Stripe-style) again — send only the keys you're changing; unset one with an empty string ("");metadata: nullclears all. (Per-keynullis retired; use"".)
2026-06-03 — Sub-organizations
Run customers as isolated child 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
- Organization control plane. New
org:admin-gated endpoints to create, read, list, patch, suspend, resume, and archive 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-Organizationheader lets anorg:adminparent key run any existing endpoint inside a direct child. See Authentication. - Child API keys. Mint, list, rotate, and revoke least-privilege keys bound to a single child (
/v1/organizations/:orgId/api-keys). Scopes must be a subset of the parent's;org:adminis 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/whoaminow returnsparentOrganizationId, andapiKeyIdis emitted in thekey_-prefixed form. - Per-child wallets + allocation. Fund a child from the parent wallet with
POST /v1/organizations/:orgId/credits/allocate(idempotent). Read a child's wallet withGET …/creditsand ledger withGET …/credits/events. Archiving a child reclaims its unspent (non-reserved) credits to the parent (reclaimedCreditson 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—monthlyCreditCap,refillThreshold,refillAmount(all in credits, nullable) plus the derivedautoRefillEnabled. A capped child's over-cap generation is denied with402 BILLING_EXHAUSTED(details.reason: "cap"); auto-refill tops a child up from the parent when itsavailabledrops 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 theGET /:orgIdsummary ascreditConfig. See the Credits concept. - Webhook firehose (
scope: "all_children"). A parentorg:adminkey can register one webhook endpoint that receives every direct child's events alongside its own, each attributable viadata.organizationId. Defaults toown; settingall_childrenrequiresorg:admin. - Migrate flat projects into children.
POST /v1/organizations/migrateatomically 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
available+reservedCreditson the credits wallet.GET /v1/creditsnow returnsavailable(grossbalancenet of credits reserved for in-flight generations) andreservedCreditsalongside the existing fields. Gate generate decisions onavailable. Purely additive — existing fields are unchanged.- New
allocationevent type on your own ledger.GET /v1/credits/eventscan now return events witheventType: "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.directionisallocateorreclaim,metadata.counterpartyOrgId(org_-prefixed) names the child, andmetadata.transferId(txn_-prefixed) joins to the allocate responseid. If your integration enumerates or switches oneventType, add a branch forallocationso transfers aren't dropped from your reconciliation. data.organizationIdon every webhook payload. Webhook delivery envelopes now includedata.organizationId(the prefixed id of the org the event belongs to) on all events, not just firehose ones. Purely additive — existingdata.*consumers are unaffected; it's what makes theall_childrenfirehose attributable.
2026-05-08 — Partner API 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
- Preview object on every content surface. Every container response now carries a normalized
previewobject withkind,primaryUrl,thumbnailUrl,imageUrls,videoUrl,hlsUrl,durationMs,aspectRatio. Same shape on get / list / job-completion /content.generatedwebhook. Backed by newcontent_containerscolumns + ffprobe + poster-frame extraction activities. - Multi-variant fan-out.
variantCountonPOST /v1/projects/:id/contentaccepts 1–5. Response iscontainerIds[]plural; billing scales linearly. - Hook is the only call-time creative input.
POST /v1/projects/:id/contenttakes a top-level requiredhookstring, used verbatim in slide 1 / overlay #1. Brand voice, language, audience, and influencer roster are drawn from project + layer state viaPATCH /v1/projects/:idand the layer / influencer endpoints — nobriefobject at the partner surface. Pull a vetted hook from the newGET /v1/projects/:id/content/hooksendpoint or send your own. - Partner asset upload. New app-media endpoints —
POST /v1/projects/:id/app-media(upload from a public URL),GET …,DELETE …/:mediaId— with per-kind mime + byte-cap validation forlogo/screenshot/demo-video. URL-based fetch through the SSRF guard; no presign / finalize dance at the partner edge. Scope:projects:write. Ungatesugc-remixpartner-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(existingads:write:connect,ads:write:overrideretained). 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 underads.*andapproval.*. - 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-routeradded to the SDK app platform enum. New install-spec generator branches with framework-specificfilesToCreate. New Next.js, Vite, React Router guides. - Server-side event forwarding. New
POST /v1/events— 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— sends a synthetic event taggedtest_event_codeand reports per-platform CAPI dispatch confirmation. Companion SDK health concept page. - CAPI per-app config wiring (CF-3 fix).
PATCH /v1/projects/:id/sdk-apps/:appIdnow writes through toproject_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-lognow returns partner-attributable rows (actor_type: "partner") with the partner'sactor_organization_idandactor_api_key_idpopulated. Every gate decision logs averdictIdreturnable on the partner write response. Backed by migration20260508000000_partner_write_safety.sql(adds'partner'to thelayer_ads_audit.actor_typeCHECK, plusactor_organization_id+actor_api_key_idonlayer_ads_auditandoptimizer_pending_actionswith partial indexes). - Approval dispatch observability. New
dispatched_at+dispatched_failure_reasoncolumns onoptimizer_pending_actions(same migration), wired to the newapproval.dispatched/approval.dispatch_failedwebhooks. Partners readingGET …/pendingsee 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. - 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.
/docs/api/examples/react-quickstart— full happy path (bootstrap → influencer → generate → poll → gallery → approve → connect → publish → metrics).
Changed
- Format enum. Partner enum is now
slideshow-builder | ugc-remix(v1 launch set).autois removed — partners must name a type.video-remix | slideshow-remixare reserved for v2 (returnUNSUPPORTED_FORMATin v1) — the surface to pass / discover a third-party source post is not yet exposed. See content items → Roadmap: source-coupled formats. Naming honesty:slideshow-builder-remixis renamed toslideshow-builderat the partner surface (the-remixsuffix was inaccurate; nothing was being remixed). Idempotency-Keyis the canonical retry mechanism. Bodyidis rejected with422 VALIDATIONon every create endpoint (project, sdk-app, influencer, content). The previous "partner-created IDs as alternative" path is gone. See Idempotency.referenceslives at the top level. Top-levelreferences = { mediaIds?, assetIds? }(each capped at 20). Pass media-library handles viareferences.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:writelegacy scope. Mapped to the union of new sub-scopes for backwards compat. New keys should mint specific sub-scopes.- Pricing page enum.
slideshow-builder50 credits /ugc-remix120. Multi-variant scales linearly.
Removed
autoformat. Was non-deterministic plumbing pretending to be a product feature.refuse_write_without_gate_trgSQL trigger (Meta + Apple variants) — previously DEFAULT-OFF backstop; the TS gate +no-ungated-platform-writeESLint rule are the only active enforcement. Cleanup in migration20260507000000_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
- Misleading 409 message on duplicate content-create — body
idis 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/keywordsfor tier-3 (D36).
2026-04-24 - Preview release
First public release.
Added
GET /v1/whoami- resolve an API key to its organization, scopes, and rate-limit tier.- Projects: create, list, get, patch, archive.
customer_external_idfor sub-tenant scoping. - GitHub install + ingest: register an installation, list visible repos, kick off
POST /v1/projects/:id/ingest/githubto generate and open an SDK-install PR. - SDK apps: create, get, patch. CAPI status per app for Meta, TikTok, Apple.
- Deterministic
install-specgenerator 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-projectrequires_approval+first_n_posts_blockedpolicy, 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. Former403 FORBIDDEN_FENCEis 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,POST /v1/jobs/:jobId/cancel. One pattern covers every long-running operation. - Idempotency-Key on every mutating POST, with partner-created resource IDs as a stronger alternative.
- Rate limits on
pilot/gic_pilot/gic_gatiers, per-endpoint-class buckets. - Kill switch - per-key, per-org, and global.
- Stable error codes with
requestIdon every response.
Known gaps
- Ads campaign, ad-set, and ad writes - planned. Reads are live.
- Trust-score approval auto-transition - planned. Today you flip
requires_approvalmanually or lean onfirst_n_posts_blocked. - Webhooks - live. HMAC-SHA256 signed, 8-attempt retry ladder, dedupe + replay. See Webhooks. Polling remains supported; webhooks are an optimization, not a replacement.
- Defense-in-depth on every publish path - gated at schedule +
scheduled-posts-publish-duetoday; IG / TikTok / Managed publish paths get rechecked in a future release. - LinkedIn + YouTube social platforms - not committed; gated on platform prioritization.