← All guides

Mock APIs for Retool β€” build the admin panel before the backend exists

The classic Retool chicken-and-egg: the internal tool is wanted now, the API it should sit on ships next sprint β€” or exists, but pointing a half-built admin panel with working Delete buttons at production data is how incidents happen. Retool's own answers are Retool Database and the built-in sample data β€” genuinely good for tabular play data, but neither is an HTTP API. Build against them and everything specific to your REST integration β€” resource config, pagination params, response shapes, transformers, error handling β€” goes unexercised until the day you swap in the real thing.

This guide points Retool's REST queries at a hosted, stateful mock instead: real endpoints, persistent writes, and failure modes you control from a URL param. Works entirely on Retool's free plan (up to 5 users, unlimited apps, no credit card) and Mockbird needs no signup at all. Every curl below was verified against production before publishing.

1. A REST query in 60 seconds

The public demo project is a seeded e-commerce API. In any app, add a query, pick the built-in REST API resource (no resource setup needed for a quick test), action type GET, URL:

https://mockbird.mockbird.workers.dev/m/demo/products?limit=20

Run it, drop a Table component, set its data to {{ query1.data }} β€” done. The response is a bare JSON array, which is exactly what the Table wants; no transformer needed. Because it's a GET, Retool re-runs the query automatically whenever a referenced input changes β€” so ?q={{ searchInput.value }} gives you live server-side search across every field with zero extra code (add _limit/sortBy/field filters the same way; the conventions are json-server compatible).

For anything beyond a scratch query, create a REST API resource with base URL https://mockbird.mockbird.workers.dev/m/demo β€” then every query in the org shares one config, and cutover to the real backend later is a one-field edit on the resource, not a hunt through every query.

2. CRUD that actually persists

The Create/Edit/Delete half of an admin panel is the half you can't build against read-only fake APIs. Here writes are real:

curl -s -X POST https://mockbird.mockbird.workers.dev/m/demo/products \
  -H 'content-type: application/json' \
  -d '{"name":"Retool guide test","price":9.99}'
# β†’ {"id":31, "name":"Retool guide test", "price":9.99}

curl -s https://mockbird.mockbird.workers.dev/m/demo/products/31   # it's really there
curl -s -X DELETE https://mockbird.mockbird.workers.dev/m/demo/products/31

In Retool that's three more queries on the same resource: a POST /products with a JSON body of {{ form1.data }}, a PATCH /products/{{ table1.selectedRow.id }}, a DELETE on the same path. Non-GET queries only run when triggered β€” wire them to button clicks, and add a success event handler that triggers the list GET so the table refreshes after every write. That refresh-after-mutation wiring is real app logic, and it only gets exercised against an API where mutations actually mutate.

3. A typed OpenAPI resource from a live URL

Every Mockbird project publishes its schema at /m/<project>/openapi.json. Retool's OpenAPI resource takes a specification URL (OpenAPI 2.0, 3.0.x, or 3.1.0) and auto-generates query fields, parameters, and docs per operation, with request bodies validated against the schema. Feed it:

https://mockbird.mockbird.workers.dev/m/demo/openapi.json

The export is OpenAPI 3.0.3 β€” the demo yields typed list/get/create/update/delete operations per resource. This is also a free rehearsal of the OpenAPI-resource workflow itself before you point it at a real internal spec.

4. Server-side Table pagination, with a real total

The Table component's server-side pagination (limit-offset based) wants two things from your API: a page of records, and β€” for numbered page controls β€” a total row count. Mockbird provides both:

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)

Wire the query's URL params to the table:

_limit  β†’  {{ table1.pageSize }}
_page   β†’  {{ Math.floor(table1.paginationOffset / table1.pageSize) + 1 }}

and set the table's Total row count to {{ query1.metadata.headers['x-total-count'] }} β€” response headers are exposed on query.metadata.headers. Numbered pagination against a real wire protocol, before the real backend exists β€” and the pattern transfers verbatim to any API that sends a total-count header.

5. Fire your failure handlers on purpose

Queries have failure event handlers and failure notifications β€” the toast, the fallback state, the "retry" button you added. They usually get tested by hoping something breaks. Make it break:

https://mockbird.mockbird.workers.dev/m/demo/products?mock_status=500

Run the query and the failure handler actually executes; remove the param and the same query is green again. One URL parameter is the whole toggle. mock_status takes any code (try 404 on the single-record GET your detail drawer uses). For "fails twice then recovers" β€” the case your own retry 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

6. The 10-second default timeout, tested

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

Retool times out queries at 10 seconds by default (adjustable per query in the Advanced tab, up to 120s on Cloud). Set a query's timeout to 2000 ms against a 3-second mock_delay and the timeout path fires on demand β€” so you can decide what the app should do when the real API has a bad day, and check that every component bound to the query shows its loading state honestly in the meantime. mock_delay goes up to 5,000 ms.

7. Rehearse 429s before the real API rations you

# allow 3 requests per 60s for your key, then 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

Internal tools are rate-limit magnets β€” one table refresh can fan out into many queries, and a JS loop over selected rows will hammer any API. The 429 comes with a real Retry-After header plus x-ratelimit-limit/remaining/reset, all readable from query.metadata.headers β€” so you can build the "back off and tell the user" behavior now instead of discovering the need in production. Use your own mock_ratelimit_key so parallel testers don't share a counter.

8. See exactly what Retool sends

When a POST "should work" but the API disagrees, the question is what actually went over the wire β€” after Retool evaluated every {{ }} in your JSON body fields. Point the query at a bin:

curl -s -X POST https://mockbird.mockbird.workers.dev/m/demo/hooks/retool-run \
  -H 'content-type: application/json' -d '{"app":"orders-admin","runId":7}'
# β†’ {"ok":true,"caught":"POST /hooks/retool-run","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 checking curly-brace interpolation, number-vs-string coercion in body fields, and which headers your resource config really adds. On your own project, one ANY /* custom route is a private bin with a per-project inspector.

9. Match your real API's response shape

If the API you're mocking wraps lists in an envelope β€” {"data": [...]} β€” your table bindings and transformers should be written against that shape from day one, or the cutover breaks them all:

curl -s '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) reshapes every list response β€” so {{ query1.data.data }} is correct in the mock and in production, and nothing needs rewriting on swap day. Details in the docs.

10. Your own API instead of the shared demo

One curl (or one click), no signup:

curl -s -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, or import an OpenAPI spec (which then feeds Β§3's typed resource), db.json, CSV, or a HAR recording of the real API. Every simulation param above works on your project's URLs. Mock JWT auth is available when the panel needs a login story. Projects have a 10,000 requests/day cap β€” admin-panel building barely dents it.

Where Retool's built-ins are still the right tool

Fair's fair: if the tool's data will live in Retool, use Retool Database β€” it's a real Postgres with a spreadsheet-ish editor, and no mock is needed because there's no API boundary to mock. Sample data is fine for learning components. Reach for a hosted mock when the finished tool will talk to an HTTP API: then the REST resource config, pagination params, response envelopes, headers, failure handling, and transformers are the work, and they deserve a target that behaves like an API β€” including on its bad days.

Related: the Appsmith version of this guide, the Bubble version, the Grafana version, the ToolJet version, the Budibase version, the Power Automate 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=2 list, POSTβ†’GET-backβ†’DELETE with id 31, _page=2&_limit=5 returning records 6–10 with x-total-count: 30, mock_status 500, 3.76s measured delay, 200 200 200 429 rate-limit run with retry-after present, seq 503β†’503β†’200β†’200, envelope {"data":[…]}, bin echo + inspector log, openapi.json serving OpenAPI 3.0.3) were run against production on 20 Sep 2026 before publishing. Retool behavior β€” GET queries auto-running on input change, non-GET queries being manually triggered, the 10s default / 120s max query timeout, response headers on query.metadata.headers, OpenAPI 2.0/3.0.x/3.1.0 resource support, free-plan limits β€” is from Retool's official docs, community-staff answers, and current pricing coverage; we don't run your Retool org. If a step here doesn't work, that's a bug: tell us.