Bubble has a rule most tools don't: in the API Connector, every call must be initialized before you can use it anywhere in the app. Bubble fires the call for real, inspects the JSON that comes back, and builds a typed schema from it β that schema is what your workflows and data sources bind to. No live endpoint returning real JSON, no call. Which means "I'll build the Bubble front end while the backend team builds the API" hits a wall on day one.
It gets sharper: Bubble's own docs warn that initialization calls "may produce actual outcomes, including the modification of live data" β initializing a POST against a real API genuinely creates whatever the POST creates β and that any sample data you type during initialization becomes part of your app's source. Initializing against production is how test orders end up in real databases.
The fix for both problems is the same: initialize against a hosted, stateful mock. Real endpoints, clean typed JSON, writes that persist but touch nothing real, failure modes you control from a URL param β and no signup. Every curl below was verified against production before publishing.
The public demo project is a seeded e-commerce API. In your Bubble editor: Plugins β API Connector β Add another API, name it, leave Authentication on None or self-handled, then Add another call:
| Field | Value |
|---|---|
| Name | get products |
| Use as | Data |
| Method / URL | GET https://mockbird.mockbird.workers.dev/m/demo/products?limit=20 |
Click Initialize call. Bubble runs the request, and the Response fields dialog lists what it detected β id and price as numbers, name/description/category/image as text. Save, and the call appears under Get data from an external API: drop a Repeating Group, set its data source to this call, bind Current cell's product's name β you're building. What the raw response looks like:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?limit=3'
# β [ {"id":1, "name":"β¦", "description":"β¦", "price":358.76, "category":"β¦", "image":"β¦"}, β¦ ]
A detail call works the same with a dynamic URL parameter β square brackets in the URL become call parameters Bubble prompts for:
GET https://mockbird.mockbird.workers.dev/m/demo/products/[id]
Set the parameter's default to 1 to initialize, and the call is ready for Current cell's product's id.
Bubble only exposes the fields it saw during initialization. Initialize against a real API whose sample record happens to have avatar: null or a missing optional field, and that field simply doesn't exist in your app β a classic API Connector trap that surfaces weeks later as "why can't I bind this?". Bubble's escape hatch is the Manually enter API response button (paste the JSON you wish the API had returned), which works but means hand-maintaining a fake payload in a dialog box.
Mockbird's seeded records always have every field populated with type-correct values (field types: numbers are numbers, emails look like emails, dates are ISO strings Bubble can type as date), so one initialization detects the full, correctly-typed schema. And because initialization sample data ships in your app's source, initializing with generated mock values instead of a copy-pasted production record is also the safer habit.
Set Use as to Action, method POST, body type JSON, with angle-bracket body parameters:
POST https://mockbird.mockbird.workers.dev/m/demo/products
Body: { "name": "<name>", "price": <price> }
Fill sample values (Bubble guide test / 12.5) and initialize. On a production API this step just created a real record β Bubble's docs warn exactly this. Here it created a mock record you can see, use, and delete:
curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/products' \
-H 'content-type: application/json' \
-d '{"name":"Bubble guide test","price":12.5}'
# β {"id":31, "name":"Bubble guide test", "price":12.5}
curl 'https://mockbird.mockbird.workers.dev/m/demo/products/31' # really persisted
curl -X DELETE 'https://mockbird.mockbird.workers.dev/m/demo/products/31'
Because writes persist, the whole create β list-refreshes β edit β delete loop of your app works end-to-end before the real backend exists β the Repeating Group bound to Β§1's Data call actually shows the record your Action just created. PATCH and DELETE calls follow the same pattern with [id] in the URL.
For server-side pagination, make _page a call parameter:
GET https://mockbird.mockbird.workers.dev/m/demo/products?_page=[page]&_limit=10
Every list response also carries the total in a header:
curl -si 'https://mockbird.mockbird.workers.dev/m/demo/products?_page=2&_limit=5' | grep -i x-total-count
# x-total-count: 30 (body: records 6β10)
Tick Capture response headers on the call before initializing and the headers are detected as response fields alongside the body β so "page X of Y" needs no second call and no COUNT endpoint. (Changing that checkbox changes the response shape; Bubble will ask you to re-initialize.)
The API Connector checkbox Include errors in response & allow workflow actions to continue exposes an error object β status code, status message, body, and a yes/no β so your workflow can branch instead of halting. It's the right setting for anything user-facing. But how do you make an API fail on cue to build those branches? One URL param:
https://mockbird.mockbird.workers.dev/m/demo/products?mock_status=500
Run the workflow and your "Only when error's status code is 500" conditions actually execute; remove the param and the same call is green. For "fails twice then recovers" β what a retry with a re-run button should survive β use a deterministic sequence:
# 1st request β 503, 2nd β 503, 3rd and later β real 200
'https://mockbird.mockbird.workers.dev/m/demo/products?mock_seq=503,503,200&mock_seq_key=myapp'
# run four times: 503 503 200 200
Rate-limit rehearsal works the same way β 3 requests then a real 429 with Retry-After:
'https://mockbird.mockbird.workers.dev/m/demo/products?mock_ratelimit=3&mock_ratelimit_key=myapp&limit=1'
# 4 requests β 200 200 200 429
(Note Bubble's warning: toggling the error-object checkbox after initializing changes the response format β re-initialize after flipping it.)
curl -s -o /dev/null -w '%{time_total}\n' \
'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_delay=3000'
# 3.77s
Add ?mock_delay=3000 (up to 5,000 ms) to any call and preview the app: now you can actually see what a Repeating Group bound to a slow external API does β and decide whether that page needs a loading indicator, a skeleton group, or This element is loading conditionals β instead of finding out when the real API has a bad day.
When a POST "should work" but doesn't, the question is what Bubble actually sent after every angle-bracket parameter was substituted. Point the call at a bin:
curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/hooks/bubble-init' \
-H 'content-type: application/json' -d '{"app":"bubble-mvp","step":1}'
# β {"ok":true,"caught":"POST /hooks/bubble-init","body":{β¦},"receivedAt":"β¦"}
The demo's catch-all route echoes what it caught, and every hit β method, path, headers, body β lands in the public demo request inspector. Useful for spotting number-vs-string coercion in body parameters and checking which headers your shared API config really adds. On your own project, one ANY /* custom route is a private bin with a per-project inspector. Bubble's Backend workflows β API workflows receiving end has the same debugging need in reverse β a bin shows you the outbound half.
The schema Bubble builds at initialization is the contract your whole app binds to. If the mock returns the same shape the real API will, cutover is editing the URL (or the shared base) β the schema still matches, so nothing needs re-initializing and no workflow needs rewiring. Two tools help the shapes match exactly:
{"data": [...]} β make the mock do it too, so Bubble detects the wrapped shape from the start:curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_envelope=data&limit=2'
# β {"data":[ {β¦}, {β¦} ]}
?mock_envelope=data (or a custom JSON template, or a project-wide default β docs) reshapes every list response.
One more Bubble-specific bonus: Mockbird demo calls qualify for the API Connector's make the call directly in the browser option (authentication None, no headers required) β data calls marked that way run from the user's browser and, per Bubble's docs, don't count against API call quotas.
One curl (or one click), no signup:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H "content-type: application/json" \
-d '{"preset":"ecommerce"}'
# β {"id":"abc123xyz9", "adminKey":"β¦", ...}
Presets: blog / ecommerce / saas β or define resources by hand. Every simulation param above works on your project's URLs. When the app needs a login story, mock JWT auth issues real signed tokens (initialize /auth/login as an Action, then send Authorization: Bearer as a shared header). Projects have a 10,000 requests/day cap β app building barely dents it.
Fair's fair: if the data belongs to your Bubble app, use Bubble's own database and data types β that's the product's core, it needs no API Connector and no mock. The API Connector exists for talking to systems outside Bubble, and that's precisely where the mock fits: when the outside API isn't built yet, isn't safe to initialize against, or won't fail on demand. Bubble's API Connector also isn't the only integration path β check the plugin marketplace first; a maintained plugin for your service may already ship initialized calls.
Related: the Adalo version, the FlutterFlow version of this guide, the WeWeb version, the Thunkable version, the Retool version of this guide, the Appsmith version, the Zapier version, the n8n version, testing loading and error states, simulating rate limits, request bins compared, and mocking third-party APIs generally.
Verification: all demo curls on this page (limit=3 list with typed price, single record, x-total-count: 30 on _page=2&_limit=5, mock_status 500, 3.77s measured delay, POSTβGET-backβDELETE with id 31, seq 503β503β200β200, rate-limit run 200 200 200 429, envelope {"data":[β¦]}, bin echo + inspector log, create-project preset) were run against production on 21 Sep 2026 before publishing. Bubble behavior β the initialize-before-use requirement, the "may produce actual outcomes, including the modification of live data" and initialization-data-ships-in-source warnings, Response fields type detection, square-bracket URL / angle-bracket body parameters, Capture response headers, the error-object checkbox and its re-initialize note, Manually enter API response, and browser-direct calls not counting against API quotas β is from Bubble's official manual and forum as of September 2026. Bubble's free plan includes the API Connector (calls consume workload units; responses are capped at 50 MB). We don't run your Bubble app; if a step here doesn't work, that's a bug: tell us.