← All guides

Mock APIs for Thunkable β€” a Web API that's safe to put in a public project

Thunkable's Web API component is how a Thunkable app talks to any REST backend. Here's the thing about it on the Free plan: per Thunkable's official pricing page (September 2026), Free includes 3 public projects β€” private projects start on paid tiers β€” and per their own docs, public projects sit in the Thunkable Public Gallery for anyone to preview, download or remix. Meanwhile the Web API docs describe the URL property as the place that "usually contains an API key". Put those together: on the Free plan, whatever you type into that component β€” URL, query parameters, headers, keys β€” ships with a project anyone can open and copy.

The fix isn't to stop building. It's to build against an API where there's nothing to leak: a hosted, stateful mock with disposable data and throwaway credentials. Prototype the whole app in public β€” lists, detail screens, forms that save, login, error handling β€” then swap one URL when the real backend (and a paid private project) exists. Every curl below was verified against production before publishing; each one is exactly what a Web API block does, so you can sanity-check any step from a terminal.

1. Your first Web API call in 60 seconds

The public demo project is a seeded e-commerce API β€” no signup, no key. In Thunkable: Blocks tab β†’ Advanced drawer β†’ βŠ• next to Web APIs, and fill the properties dialog:

PropertyValue
URLhttps://mockbird.mockbird.workers.dev/m/demo/products?limit=20
QueryParameters / Body / Headersleave empty for now

Then in blocks: when Button.Click β†’ call Web_API1's Get. The Get block hands you three outputs β€” response, status, and error. What comes back:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?limit=3'
# β†’ [ {"id":1, "name":"Bridge Area Night", "description":"…", "price":358.76,
#      "image":"…", "category":"clothing", "inStock":true, "rating":4.6}, … ]

Every field of every seeded record holds a type-correct value (field types), so your parsing blocks never hit the missing-field surprises that free placeholder APIs love to spring.

2. Parsing it with Object blocks (lists are 1-indexed)

Thunkable's pattern: convert JSON to object, then get property of object. Their docs' dot-path syntax works here, with one thing worth engraving on your monitor β€” Thunkable list indices start at 1 (their own example is rows[1].elements[1].duration.text):

get property "[1].name"  of (convert JSON to object: Web_API1's response)   # β†’ "Bridge Area Night"
get property "[1].price" # β†’ 358.76
get property "[2].category" # β†’ second product's category

To fill a Listviewer: loop for each item in list over the response object and make text from each item's name property β€” or bind a Data Viewer List if you're in the drag-and-drop builder. Because the data is stable between the daily reseeds, blocks that worked yesterday parse the same shape today.

3. Server-side pagination, sorting, and search from blocks

The Web API component's set QueryParameters block accepts a create object block β€” one property: value pair per parameter β€” and Mockbird understands a full query toolkit (docs), so the server does the work your blocks would otherwise fake with list gymnastics:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?page=2&limit=5'
# β†’ records 6–10
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?sortBy=price&order=desc&limit=3'
# β†’ prices e.g. 926.11, 913.03, 792.85 β€” highest first
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?q=area'
# β†’ free-text search across fields, e.g. "Service Area"

In blocks: set Web_API1's QueryParameters to (create object: page = App_page, limit = 5, sortBy = "price", order = "desc"), wired to variables your Previous/Next buttons increment. One component definition, every page of data.

4. Totals in the body β€” where blocks can reach them

The Get block gives you response, status, and error. Not response headers β€” so a total-count header is invisible to Thunkable. ?mock_envelope reshapes the response to put pagination metadata in the body, where get property can reach it:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_envelope=%7B%22total%22%3A%22%24total%22%2C%22page%22%3A%22%24page%22%2C%22hasMore%22%3A%22%24hasMore%22%2C%22items%22%3A%22%24data%22%7D&limit=3'
# β†’ {"total":30, "page":1, "hasMore":true, "items":[ …3 products… ]}

(That's the URL-encoded template {"total":"$total","page":"$page","hasMore":"$hasMore","items":"$data"} β€” set it once as a project-wide default in settings and the URL stays clean.) Now get property "hasMore" decides whether your Load-more button shows, and get property "items[1].name" reads the list. If the real API you'll eventually use wraps its lists β€” most do β€” matching its envelope now means your parsing blocks survive cutover untouched.

5. A form that saves: Post, then prove it stuck

The Web API component has Post, Put, Patch, and Delete blocks. Set the Body with generate JSON from object, plus a Content-Type header:

curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/products' \
  -H 'content-type: application/json' \
  -d '{"name":"Thunkable test product","price":19.99,"category":"toys","inStock":true}'
# β†’ 201 {"id":31, "name":"Thunkable test product", "price":19.99, …}

Unlike placeholder APIs that answer 201 and forget you instantly, this write persists β€” a follow-up Get on /products/31 returns your record, and a Delete block removes it for real (we verified the full POST β†’ GET-back β†’ DELETE β†’ 404 cycle before publishing). So your create-form screen, your detail screen, and your delete button all get exercised against genuine round-trips. Demo data resets every 24 h, which for a public-project prototype is a feature: nothing you write matters tomorrow.

6. Error branches you can fire on purpose

Every API block reports status and error outputs β€” but you can't design the failure path if the API never fails. Make it fail on cue:

curl -s -o /dev/null -w '%{http_code}\n' \
  'https://mockbird.mockbird.workers.dev/m/demo/products?mock_status=500'   # β†’ 500
# body: {"error":"simulated 500 error (mock_status)"}

Wire an if status = 200 … else … branch and watch your error Label actually appear. For retry logic, ?mock_seq serves a deterministic sequence β€” fails twice, then recovers:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_seq=503,503,200&mock_seq_key=my-test-1'
# 1st call β†’ 503, 2nd β†’ 503, 3rd β†’ 200, then 200s

A loop block that retries while status β‰  200 (cap it!) can now be tested end-to-end, every run identical.

7. Loading states that last long enough to see

Show a spinner when the button is clicked, hide it when Get returns β€” but against a fast API the spinner flickers for 40 ms and you never see whether the layout works. ?mock_delay holds the response open:

curl -s -o /dev/null -w '%{time_total}s\n' \
  'https://mockbird.mockbird.workers.dev/m/demo/products?mock_delay=3000&limit=1'
# β†’ 3.75s

Three real seconds to check the Loading Icon is visible, the button is disabled, and nothing overlaps β€” then take the parameter off.

8. A login flow with real JWTs β€” no auth backend

Thunkable's community has long asked for auth helpers in the Web API component; the workaround is plain blocks, and it's enough. Every Mockbird project exposes mock auth endpoints β€” any email/password pair logs in and returns a real signed JWT:

curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/auth/login' \
  -H 'content-type: application/json' \
  -d '{"email":"tester@example.com","password":"anything"}'
# β†’ {"user":{…}, "token":"eyJhbGciOi…"}   (a real HS256 JWT)

Blocks: Post to /auth/login with a generated-JSON body from your TextInputs β†’ get property "token" β†’ store it in a stored variable β†’ on later calls, set Web_API1's Headers to (create object: Authorization = join "Bearer " stored token). /auth/me returns the logged-in user, and tokens expire on a schedule you control β€” so you can even test the token-expired path. And because none of it is a real credential, it can sit in a public remixable project all day.

9. See exactly what your blocks sent

When a Post misbehaves, the question is always: what did Thunkable actually transmit? Point the block at a catch-all bin path and look:

curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/hooks/from-thunkable' \
  -H 'content-type: application/json' -d '{"hello":"blocks"}'
# β†’ {"ok":true, "caught":"POST /hooks/from-thunkable", "body":{"hello":"blocks"}, …}

The response echoes what arrived, and the request inspector (public for the demo project) logs method, path, headers, and body β€” so "is my Body block sending a string or JSON?" takes ten seconds to answer instead of a forum thread.

10. Your own project, one curl (or one click)

The shared demo is fine for a first screen; for your own schema, create a project β€” no signup:

curl -X POST 'https://mockbird.mockbird.workers.dev/api/projects' \
  -H 'content-type: application/json' -d '{"preset":"ecommerce"}'
# β†’ {"id":"abc123xyz9", "adminKey":"…", …}  β†’ your API: /m/abc123xyz9/products

Or use the one-click dashboard link. Presets: blog, ecommerce, saas β€” or define your own resources, import a CSV, or import an OpenAPI spec. Keep the adminKey out of your Thunkable project: it's only for managing the mock (schema, seeding), never needed by the app's Web API calls. The public mock URL is the only thing your blocks see β€” and that URL is safe to publish by design.

11. Cutover day

When the real backend exists (and, if secrets are involved, your project has moved to a paid private plan), the swap is the Web API component's URL property β€” one edit, since query params, body shapes, and auth headers were all real from day one. If you matched your backend's envelope in Β§4, even the parsing blocks stay put. Until then, every screen of the prototype works, in public, with nothing worth stealing.

12. Where Thunkable's own data options win

Honesty section: for user-generated data your app owns, Thunkable's built-in options β€” local Data Sources, stored variables, and the Airtable/Google Sheets integrations β€” are the home turf, with drag-and-drop Data Viewer binding and no HTTP at all. A mock API earns its keep when the app's job is to talk to a REST backend: yours-but-unbuilt, a third-party API you don't want keyed in a public project, or any flow where you need pagination, auth, failure, and latency to behave like production. Use the right tool per screen; they compose fine in one app.

Related: the Adalo version, the FlutterFlow version of this guide, the Bubble version, the WeWeb version, testing loading and error states, mock JWT auth, and mocking third-party APIs generally.

Verification: all demo curls on this page (limit=3 typed list, page=2&limit=5 returning records 6–10, sortBy=price desc, q=area search, custom mock_envelope returning {"total":30,"page":1,"hasMore":true,"items":[…]}, POSTβ†’GET-backβ†’DELETEβ†’404 with id 31, mock_status 500, seq 503β†’503β†’200β†’200, 3.75s measured delay, auth login token + /auth/me user, bin echo, create-project preset β€” created and deleted) were run against production on 21 Sep 2026 before publishing. Seeded values like names and prices vary with the demo's daily reseed. Thunkable facts β€” Free plan = 3 public projects with private projects on paid tiers (official pricing page), public projects viewable/remixable by anyone in the Public Gallery (their projects docs and FAQ), the Web API component's properties and Get/Post/Put/Patch/Delete blocks with response/status/error outputs, 1-indexed dot paths, and query-params-via-create-object (their Web API docs) β€” are from Thunkable's official docs and pricing as of September 2026. We don't run your Thunkable app; if a step here doesn't work, that's a bug: tell us.