Builder.io: a real curl, the Fusion/Publish split, and where the visual editor actually lands

Builder.io is the visual-editor headless CMS the React community keeps coming back to. We spent a session on 2026-05-07 doing the basic developer-evaluation walk: open /m/pricing, hit cdn.builder.io/api/v3/content/page with a fake key to see the unfiltered JSON, read the Content API docs, and check what "Fusion" and "Publish" actually mean now that the marketing has reframed the product around an AI-first IDE. Here's the verbatim 401 we pulled, the pricing tab structure, and where Builder.io sits next to Storyblok, Contentstack and the open-source three.

Honest about what this is

We're the team behind SimpleReview, a Chrome extension that drafts a code-fix PR on whatever element you click on a broken admin or storefront. We are not affiliated with Builder.io, not partners, not customers, not investors. This page is a scouting note from one real evaluation session on 2026-05-07: public marketing site, public docs, the actual cdn.builder.io CDN endpoint hit with curl. We did not pay for a seat. If we got something wrong, open a GitHub issue and we'll fix it.

Friction 1 — the pricing page is a tab

Most pricing pages are a tier ladder you can absorb in ten seconds. Builder's /m/pricing opens with a headline that reads "Select a plan for your team" and a subhead that does the actual product positioning: "Start with either Fusion or Publish. Combine both into the ultimate AI-accelerated workflow." Underneath that, before any tier card, there's a tab toggle: Fusion (Visual IDE) on the left, Publish (Visual CMS) on the right.

Builder.io pricing page on 2026-05-08: dark background, headline 'Select a plan for your team', Fusion and Publish product tabs, annual toggle, and four visible pricing cards
Captured again 2026-05-08 from https://www.builder.io/m/pricing via headless Chrome at 1440×950 after accepting the cookie prompt. The first thing you have to do on the pricing page is pick a product axis — Fusion (the AI design-to-code IDE) or Publish (the headless CMS). Pricing is per-axis.

The split matters. Fusion is the AI-pipeline product — Figma-to-code, brand-aware generation, the agent stack — pushed hard since the 2024-2025 design-to-code wave (same era that fed v0 / Lovable / Bolt). Publish is the older, more familiar Builder.io: visual page builder + headless content delivery via cdn.builder.io, the side the existing customer base integrates against in production.

Once you pick a tab, the tier cards are the standard four-column SaaS layout: Free, Pro, Team, Enterprise. Free and Pro CTAs route to self-serve signup; Enterprise routes to Contact sales.

Builder.io compare plan features section captured 2026-05-08 at 1440 by 950. Four columns: Free, Pro, Team, Enterprise, with Monthly Usage and Key Features rows visible.
The tier cards on the same page, scrolled past the hero. The four-column "Free / Pro / Team / Enterprise" layout is the canonical SaaS shape. Monthly numbers live behind the "Compare plan features" matrix below; the cards themselves carry only persona descriptions, not prices.
What's hidden vs what's shown

Unlike Contentstack (which hides everything behind "Contact us"), Builder shows you Free and lets you self-serve into a sandbox. Where it gets coy is the Pro and Team monthly amount — the cards don't carry a $X/month number. You have to expand the "Compare plan features" matrix and read the Monthly Usage row to see numbers like "$25 per 500 Agent Credits" pay-as-you-go and "500 monthly Agent Credits" included. The unit of pricing is not seats — it's Agent Credits, which is the AI generation budget, not document count or page views. That changes the buyer math significantly: the question stops being "how many editors" and becomes "how much does our team generate with the AI."

The Free tier is generous enough to actually evaluate the editor: up to 5 users per space, 25 daily / 75 monthly Agent Credits, GitHub/GitLab/Bitbucket integration, public previews. Enough to wire a Next.js app against a real Builder space without paying. We didn't sign up in this session — we wanted to characterize the surface a developer hits first — but the gate is materially lower than Contentstack's.

The first-hand artifact — what cdn.builder.io actually returns

The pricing page can be a tab. The production CDN cannot. Builder's Content API is the same endpoint every customer's frontend calls, and you can hit it without an account because the auth is a query-string apiKey. Public CDN, public response, no rate-limit on the unauthenticated path before the 401:

$ curl -sD- -o /tmp/resp.json \
    "https://cdn.builder.io/api/v3/content/page?apiKey=fake_invalid_key_test_12345"

HTTP/2 401
content-type: application/json; charset=utf-8
content-length: 103
x-builder-long-cache-setting: -1
x-powered-by: Express
access-control-allow-origin: *
access-control-allow-headers: content-type, accept, authorization,
  x-builder-sdk, x-builder-sdk-gen, x-builder-sdk-version,
  sentry-trace, baggage
access-control-allow-methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
x-request-id: 1d686210-4aa7-11f1-9f56-ed42afe9474f
etag: W/"67-xZqoQ28XGZwrYSYebFoYpA8DNzI"
server: Google Frontend
via: 1.1 varnish, 1.1 102b88467fa873b1937f2ab6cab80ac0.cloudfront.net (CloudFront)
x-served-by: cache-fra-etou8220068-FRA
x-builder-custom-fastly-maxage: -1
x-cloudrun-origin: primary
vary: Authorization
x-cache: Error from cloudfront
x-amz-cf-pop: HEL51-P6
date: Fri, 08 May 2026 06:28:23 GMT

Body of the response, verbatim:

{
  "status": 401,
  "message": "Authorization required",
  "detail": "For help please contact [email protected]"
}

A few things you can read off this response without buying a seat:

  • The infra is multi-layered. via: 1.1 varnish, 1.1 ... cloudfront.net (CloudFront) plus server: Google Frontend plus x-cloudrun-origin: primary means: Cloudflare/Fastly Varnish at the edge → CloudFront in front of that for some routes → Google Cloud Run as the origin. Three providers stacked. That's a lot of failure surface, but it's also a lot of redundancy.
  • The error shape is small and stable. Three keys: status, message, detail. No field-level error array (Contentstack's CDA returns one), no rate-limit headers visible, no per-resource hint. If you write a generic error handler in your SDK, this is what it has to parse — and a real Builder client should treat 401 + this body as "the apiKey is wrong or the project moved" before bothering the user.
  • CORS is wide open and well-documented. The access-control-allow-origin: * plus the long allowed-headers list including x-builder-sdk, x-builder-sdk-gen, x-builder-sdk-version tells you the SDK identifies itself per request. That's how Builder tracks SDK adoption and rolls out per-version bug fixes.
  • The cache layer is hostile to errors, by design. x-cache: Error from cloudfront plus x-builder-long-cache-setting: -1 means failed responses are explicitly not cached. So a 401 storm from a misconfigured client doesn't poison the CDN, but it also doesn't get any backpressure relief.
  • Geo: x-served-by: cache-fra-etou8220068-FRA and x-amz-cf-pop: HEL51-P6. Frankfurt + Helsinki POPs from our Hetzner FRA-region box. Latency to first byte was sub-150ms on the warm path.

If you drop the apiKey entirely — same 401, same body. Auth is enforced at the edge, not at the origin, which is the right call for a CDN-fronted API.

The Content API surface (what a real integration looks like)

Per the public docs at builder.io/c/docs/content-api, the request shape is one URL and one parameter:

GET https://cdn.builder.io/api/v3/content/<model>?apiKey=<publicKey>

The model name is whatever you defined in the Builder space (page, blog-post, landing, navigation, etc.). The API key is the public read key per space. Notably the docs flag a casing rule: "You must apiKey, not apikey". Lowercase fails. That's a common foot-gun on case-sensitive runtimes; it's worth pinning in a constant.

Builder.io Content API documentation page captured 2026-05-08 at 1440 by 950. Sidebar lists API surface, main column shows Set up, framework selector, Next.js Pages Router selector, Gen 1 selector, and npm install command.
The Content API reference at builder.io/c/docs/content-api, refreshed 2026-05-08 at 1440×950. The sidebar inventories the full API surface (Content, HTML, GraphQL Content, Image, Write, Upload, Admin, Assets). The framework + meta-framework + SDK-Generation triple selector at the top of the body is a notable docs UX choice — the same page rewrites itself for React vs Vue vs Qwik vs Web Components.

The interesting choices in the Content API surface:

  • v3 is the default; older SDK generations are still documented. The "SDK Generation" dropdown ("Gen 1" by default) means you can read the right code samples for legacy projects without having to time-travel through Git tags. Most CMS docs sites force you to pick a current version and bury the older one.
  • GraphQL is offered alongside REST. The "GraphQL Content API" entry in the sidebar is real — the same content is queryable via GraphQL if you prefer it for client-side composition. We did not test the GraphQL endpoint in this session.
  • HTML API and Image API are first-class. Builder ships a managed image pipeline and an HTML-rendered output, not just JSON. That's the part of the product that competes with Contentful's Compose and Storyblok's visual editor — it's why a non-React stack can still use Builder.
  • Write API and Admin API exist publicly. Most CDN-fronted CMSes hide the write side behind a different domain or behind partner agreements. Builder publishing it openly says they expect content to flow in from automation pipelines, not just the visual editor.

The framework selector at the top of every API page (React, Vue, Svelte, Qwik, Angular, Solid, Web Components) is the visible tip of Builder's bigger architectural bet — Mitosis.

The Mitosis story (and why it matters here)

Mitosis is an open-source compiler the Builder team published in 2022 (and has continued investing in since): one component source written in a JSX-like dialect compiles down to React, Vue, Svelte, Solid, Qwik, Angular and several other targets. It's MIT-licensed, in a public monorepo at github.com/BuilderIO/mitosis, and powers the multi-framework SDK output Builder ships.

Mitosis is the answer to the question every visual editor has to face: "will this lock me into one frontend framework?" Storyblok and Contentstack answer with framework-agnostic JSON — you bring the render layer. Builder answers with: we wrote a compiler so the same visual component renders on whatever framework you bring. The trade is real (Mitosis-compiled components have constraints; no arbitrary hooks the compiler can't reason about) but the upside is one source producing idiomatic output across seven targets. For a multi-stack org — React storefront + Vue admin + Svelte landing site, common in companies that grew by acquisition — that's a real wedge. For a single-framework shop it's overhead you don't need.

Honestly, next to Storyblok / Contentstack / Strapi / Directus

We've done the same scouting walk on the open-source side and on Storyblok and Contentstack the same week. The contrast is the article:

DimensionBuilder.ioStoryblokContentstackStrapi / Directus / Payload
Time to first curl against your own data ~5 min: free signup → space → public apiKey. ~5 min: free signup → preview token. Behind a sales call. ~10 min from docker run.
Public Free tier Yes — 5 users, 25 daily Agent Credits. Yes — limited content entries. "Start free" exists but funnels to demo. Self-host: free. Cloud: published numbers.
Visual editor depth Highest — drag/drop on top of your own React/Vue components, with AI generation. Strong — visual editor on a preview iframe. Strong — gated behind the wall. Form-based admin. No "drag on the live page" mode out of the box.
Multi-framework SDK Mitosis-compiled: React, Vue, Svelte, Qwik, Angular, Solid, Web Components. Per-framework SDKs maintained separately. Per-framework SDKs maintained separately. JSON-first. You bring the SDK.
Pricing unit Agent Credits (AI generation budget) + seats. Per-seat + entries + bandwidth. Custom-quote / per-seat. Free self-host; cloud is per-seat / per-project.
Vendor lock-in Medium-high. Content lives in their stack; SDK is open but compiled output is theirs. High. Content lives in their stack. High. Same. Low. You own the database.
AI surface Highest — Fusion is the headline product. Figma-to-code, brand-aware generation, agent workflows. Add-on AI features. Editor-side assistant. "Brand-aware AI" + "agentic foundation." Mostly conference-marketing as of this scout. BYO LLM via plugins. Friction higher; control complete.

None of this is a verdict. It's positioning. Builder.io is the most opinionated about how you'll build pages — its differentiator is the visual + AI layer, and the price is the vendor relationship that comes with it. Storyblok is the same shape with less AI ambition and more conservative tooling. Contentstack is enterprise procurement. The open-source three are the answer when you want to own the database and write your own admin chrome.

Things we'd change in the developer evaluation flow

  1. Show one Pro/Team monthly number on the tier cards. The Fusion/Publish tab + the four-column persona row is fine, but a developer skimming for a budget conversation needs a $X/month anchor before the "Compare plan features" matrix unfolds. Hiding it costs you the comparison-spreadsheet shortlist.
  2. Pin the casing rule on every Content API code sample. The "apiKey, not apikey" warning is a 30-minute debugging session for someone copying from a Slack message. Inline the constant in the sample, don't paragraph-warn.
  3. Document the rate-limit shape. The 401 response carries no rate-limit header; the docs page we read didn't enumerate per-second / per-month caps. A real production integration needs to know whether the limit is per-IP, per-key, per-org, or none, and what the 429 body looks like. Right now you find out by hitting it.
  4. Keep the per-framework SDK selector. The framework + meta-framework + SDK-Generation triple at the top of every API page is a quietly excellent docs UX choice — most vendors should copy it.

What we'd actually do

If we had a marketing-led team that wanted non-engineers shipping landing pages onto a React or Next.js site, and the team had a budget for AI generation credits, we'd put Builder.io on the shortlist next to Storyblok. The visual editor on top of your own components is the right shape for that team and the AI-design-to-code path is real (not just marketing). If we had a small startup picking a CMS for the next two years of product, we'd self-host one of Strapi, Directus, or Payload, build a bespoke admin in our own stack, and revisit Builder once we had revenue and a non-dev editorial team. The Mitosis bet is interesting either way — even if you don't use Builder, watching how the multi-framework compiler evolves tells you where the design-system tooling space is heading.

Where this fits

Adjacent scouting notes from the same week: Storyblok — visual editor SaaS scout, Contentstack — behind the "Contact us" wall, Strapi — open-source headless default, Directus — SQL-first with a real admin, Payload — TypeScript-first headless, Ghost — the headless-blog wedge. SimpleReview is the Chrome extension that turns whatever element you click on a broken admin or storefront into a draft code-fix PR — it works on a Builder.io-rendered front end the same way it works on a Strapi one, because by the time the page renders it's just HTML.

Demo: SimpleReview on a Builder.io issue

~/$ curl https://cdn.builder.io/api/v3/...
SimpleReview
$ curl -D - \
   'https://cdn.builder.io/api/v3/content/page?apiKey=fake_invalid_key'
 
< HTTP/2 401
< x-amz-cf-pop: HEL51-P6
< via: 1.1 Fastly Varnish
< x-served-by: cache-fra-etou8220068-FRA
⨯ Authorization required
{"status":401,"message":"Authorization required",
 "detail":"For help please contact [email protected]"}
request id: 1d686210-4aa7-11f1-9f56-ed42afe9474f
✓ HTTP 200 · results array · cached at edge
$ curl -D - \
   'https://cdn.builder.io/api/v3/content/page?apiKey=$BUILDER_PUBLIC_KEY'
 
< HTTP/2 200
< x-cache: HIT, MISS
< cache-control: public, max-age=300
{"results":[{"id":"...","name":"home","data":{...}}]}
Comment×
apiKey is the public key, not secret|
Fix it ✓ Done
waiting for selection…
Detected
Status401
EdgeCloudFront+Fastly
Fix plan
Builder.io's apiKey is a PUBLIC key (safe in client). Replace placeholder with $BUILDER_PUBLIC_KEY env var.
Result
200 with results. Edge-cached 5 min. Multi-CDN stack visible.
✓ Fix ready
fix(builder): use $BUILDER_PUBLIC_KEY
1 line · client.ts
Click SimpleReview → select Authorization requiredFix it → public apiKey wired in