API Changelog

Track all API changes, new endpoints, deprecations, and improvements across versions.

API Changelog

Track all changes, updates, and improvements

v3.7

minorLatest
August 19, 2026

🔒 Security Updates

  • sourceTemplateUserId is server-owned on both project write paths. It is set only when a project is instantiated from a template; a client-supplied value is now ignored, closing a path by which a caller could point a project at another account's asset namespace.
  • The AI, workflow and MCP endpoint families do not accept X-API-Key. They require a Supabase session, or a scoped MCP connection token for the transport — an SDK API token is owner-scoped across every project and carries no per-tool scopes.
  • GET /share/:projectId/:token no longer returns the owner's credentials. projectSettings is one blob mixing render configuration with secrets, and the public share payload was returned verbatim — so anyone holding a share link (an unauthenticated URL that ships inside every embed snippet) could read the project's SMTP password, webhook HMAC signing secret and WooCommerce / Shopify API credentials. Those fields are now stripped from the public response. No rendering field is affected; branding, lighting, camera, AR and pricing-display settings are unchanged.
  • PUT /profile now writes through a strict allowlist (firstName, lastName, companyName, taxId, avatarUrl, preferredLocale, phone, website) and pins email to the authenticated identity. It previously merged the request body over the stored profile, which is the input to both the billing resolver and the admin check — so any authenticated caller could self-grant isSuperAdmin, an enterprise plan, or a complimentary account. Unknown keys are STRIPPED rather than rejected, so a client that reads a profile, edits one field and sends the whole object back keeps working.

✨ New Features

  • Optimistic concurrency on project writes. PUT /projects/:id and POST /projects/:id/update accept an optional baseUpdatedAt (the updatedAt you read with the project). When present, a write against a row that has moved since is refused with HTTP 409 { success: false, conflict: true, error, serverUpdatedAt } instead of applied. Strictly opt-in — omit it and behaviour is unchanged. The sendBeacon variant drops a stale write server-side (bare 409, no body) because an unloading page cannot react to a response.
  • New "AI & Agent APIs" reference section covering the three authenticated families added since v3.6: the Tool Layer (/ai/tools, /ai/tools/execute, pending-change apply/cancel, revision restore), agent workflows (/ai/workflows and its start/advance/answer/pause/cancel/approval/catalogue routes), and MCP connection management (/mcp/connections, plus the /mcp transport). Full tool catalogue, scopes and error taxonomy live at /docs/mcp.
  • GET /assets/:assetId/download accepts an opt-in ?mode=signed-url, which returns { success, signedUrl, fileName } instead of streaming the file. The browser then fetches the bytes directly from storage, so a large model no longer buffers through the API and cannot hit the 150s request ceiling. The DEFAULT is unchanged and still the raw file — that response is a published contract. Feature-detect on Content-Type: an older deployment ignores the parameter and returns binary.

⚡ Improvements

  • POST /projects/:id/pricing-blocks/calculate now applies number-input per-unit pricing (value × price) and conditional-visibility gating, which it previously skipped. TOTALS CHANGE for projects using either — re-baseline snapshot tests that assert an exact total. The response gains hiddenOptionBlockIds (blocks a rule hid, excluded from the total) and engineApplied { numberInputPricing, conditionalGating }, so a caller can tell what was applied rather than assuming it. Both additive; older deployments omit them.
  • Documented what the calculate engine still does NOT do: palmako-price resolves to 0, gating reads only is-selected / is-not-selected (so a block hidden by a numeric, sourceModularSide or modular-sourced condition is still priced), and there is no per-line breakdown or SKU resolution.
  • GET /projects/:id now returns sceneData.materialDictionary in the uncompressed branch. Between April and August 2026 that branch omitted it, so a headless consumer received variants carrying an unresolvable _materialRef and no PBR data. A server test now pins every serialized scene field to the response shape.
  • GET /projects/:id is served Cache-Control: no-cache (it was briefly max-age=30). The project row is written by several actors, so an age-cached body can be older than a change you just made. The ETag is unchanged — revalidate with If-None-Match.
  • The outbound fetch behind the asset-download modelUrl fallback is now time-bounded. It previously had no timeout against a host we do not control, so a stalled upstream pinned the handler until the gateway killed it.

🐛 Bug Fixes

  • Fixed a server-side hang on every endpoint that reads a project whose scene is stored compressed (any scene at or above the 128 KB threshold — most real configurators). GET/POST /projects/:id/option-blocks, /pricing-blocks, POST /option-blocks/evaluate, POST /pricing-blocks/calculate, /form-fields, /animation-blocks and /settings all decompress the row server-side, and the decompression helper awaited a write to a DecompressionStream before any reader was attached — a promise that never resolves. The request then idled silently to the 150s gateway timeout (HTTP 504) with zero bytes sent. Projects under the threshold are stored uncompressed and answered in ~50 ms, which is why it looked intermittent rather than broken. GET /projects/:id was NOT affected (it reads the row without decompressing).

v3.6

minor
August 11, 2026

v3.5

minor
August 10, 2026

v3.4

patch
April 8, 2026

v3.3

minor
April 2, 2026

v3.2

minor
March 11, 2026

v3.1.0

minor
February 27, 2026

v3.0.0

major
January 15, 2026

Continue reading