The project id demo is a shared, always-on sandbox seeded with the e-commerce preset (products, orders, customers, reviews). Full CRUD works on it and it resets every 24 hours, so feel free to write to it:
curl "https://mockbird.mockbird.workers.dev/m/demo/products?limit=3"
curl "https://mockbird.mockbird.workers.dev/m/demo/orders?status=shipped&_expand=customer"
curl -X POST "https://mockbird.mockbird.workers.dev/m/demo/products" -H 'content-type: application/json' -d '{"name":"My Widget","price":9.99}'
The demo also ships three showcase custom routes: GET /m/demo/health, ANY /m/demo/echo (reflects method, query and body), and GET /m/demo/config/:key.
Opening a /m/demo/β¦ URL in a browser tab shows a friendly endpoint explorer (live status + body + copyable curl) instead of raw JSON. Code is never affected β curl, fetch() and crawlers always get the raw response β and your own projects always serve raw JSON everywhere. Append ?mock_raw=1 to see the raw body in a browser.
Scripts that hardcode httpbin.org can just swap the host: /m/httpbin/* serves the httpbin greatest hits with the same paths and response shapes β /get, /post (+ put/patch/delete), /anything/*, /headers, /ip, /user-agent, /uuid, /status/:codes, /delay/:n, /base64, /json//xml//html, /basic-auth, /bearer, /redirect/:n, /response-headers, /cookies, /stream/:n. No signup, no key, CORS on, shared 50k/day cap.
curl "https://mockbird.mockbird.workers.dev/m/httpbin/get?x=1" # httpbin-shaped echo
curl "https://mockbird.mockbird.workers.dev/m/httpbin/status/500" # any status
curl "https://mockbird.mockbird.workers.dev/m/httpbin/delay/3" # seconds, max 10
GET /m/httpbin lists every supported endpoint. Full details + what's deliberately omitted: httpbin alternative guide.
Code that hardcodes randomuser.me can also just swap the host: /m/randomuser/api serves the same response shape (name.title, location.street.number, login.uuid, dob.date, picture.largeβ¦) with the same params β results (max 100), seed, page, gender, nat, inc/exc, noinfo=1. Fully deterministic: same seed+page β identical users, forever. Picture URLs are deterministic SVG initials avatars served under /m/randomuser/api/portraits/β¦ (same path shape as theirs); the md5/sha1/sha256 login fields are stable dummy hex, not real digests. JSON only.
curl "https://mockbird.mockbird.workers.dev/m/randomuser/api/?results=3&seed=demo&nat=us,gb"
curl "https://mockbird.mockbird.workers.dev/m/randomuser/api/?inc=name,email,picture&noinfo=1"
GET /m/randomuser is the self-describing index. Want fake users you can write to? See the randomuser.me alternative guide.
fakerapi.it has been answering 502 since Aug 29, 2026 (live status). Code that hardcodes it can swap the host: /m/fakerapi/api/v2/<resource> serves the same ten resources (persons, users, addresses, companies, books, products, texts, images, places, credit_cards) plus the custom builder (/api/v2/custom?id=counter&name=firstName&mail=email), with the same params (_quantity β max 100, theirs 1000 β _locale, _seed, _gender, _birthday_start/_end, _price_min/_max, _taxes, _categories_type, _characters, _type/_width/_height) and the same {status, code, locale, seed, total, data} envelope (/api/v1/β¦ omits locale/seed, like the original). _seed is deterministic β the same seed returns the same rows forever. Honest notes: _locale is echoed but data stays English (their 40+ real locales are their win), text fields are generated word text, and image URLs point at our deterministic SVG placeholders β which actually load, unlike FakerAPI's dead placeimg.com/placekitten.com hosts.
curl "https://mockbird.mockbird.workers.dev/m/fakerapi/api/v2/persons?_quantity=3"
curl "https://mockbird.mockbird.workers.dev/m/fakerapi/api/v2/persons?_quantity=2&_seed=42"
curl "https://mockbird.mockbird.workers.dev/m/fakerapi/api/v2/custom?_quantity=3&id=counter&name=firstName&mail=email"
GET /m/fakerapi is the self-describing index. Want fake data that persists (single-record GETs, filters, writes)? See the FakerAPI alternative guide.
Every resource lives at /m/<project>/<resource>. All endpoints are CORS-enabled and need no API key. Every read endpoint also answers HEAD with the same status and headers (incl. X-Total-Count) and no body β friendly to curl -I, link checkers and uptime monitors.
GET /m/abc123/users # list (paginated, default 20)
GET /m/abc123/users/5 # one record
POST /m/abc123/users # create (JSON body) β assigns next id
PUT /m/abc123/users/5 # replace
PATCH /m/abc123/users/5 # merge fields
DELETE /m/abc123/users/5 # delete
Lost? GET /m/<project> (the bare project URL) returns a JSON index of the
whole API: every resource with record counts and URLs, custom routes, auth status, and
export links (OpenAPI, GraphQL, Postman, TypeScript). Try it:
curl https://mockbird.mockbird.workers.dev/m/demo
| Param | Meaning | Example |
|---|---|---|
page, limit | pagination (limit β€ 100); total in X-Total-Count header | ?page=2&limit=10 |
sortBy, order | sort by any field, asc/desc | ?sortBy=price&order=desc |
sortBy=random | shuffled order β with limit=1 you get one random record (a quotable-style /random endpoint; caveat: a field literally named random can't be sorted on) | ?sortBy=random&limit=1 |
search | substring match across all fields | ?search=lisbon |
_page, _limit, _sort, _order, q | json-server-style aliases for the params above β paste json-server URLs unchanged | ?_sort=price&_order=desc&_limit=5 |
| any field name | exact-match filter. Repeat the param to OR values β ?id=3&id=5 returns both records (json-server-style batch fetch) | ?city=Tokyo&inStock=true, ?id=3&id=5 |
<field>_gte / _lte / _gt / _lt | range filters (json-server style). Numbers compare numerically; strings and ISO dates compare lexicographically (which is correct for ISO dates) | ?price_gte=100&price_lt=500, ?placedAt_gte=2026-01-01 |
<field>_ne | not-equal filter (records missing the field match too) | ?status=pending β ?status_ne=pending |
<field>_like | case-insensitive substring match (note: json-server treats _like as a regex; ours is a plain substring β safer, and what mockapi.io's search does) | ?name_like=chair |
mock_delay | simulate slow network (ms, β€5000) | ?mock_delay=2000 |
mock_status | force an error response (400β599) to test error handling | ?mock_status=500 |
mock_chaos | chaos injection: fail that fraction of requests with a random 500/502/503/504/429 β test retry & backoff logic. Injected failures carry a x-mockbird-chaos: injected response header; override the status pool with mock_chaos_status=500,503 | ?mock_chaos=0.3 |
mock_jitter | random added latency in a ms range (800 = 0β800 ms); composes with mock_delay, total β€5000 ms | ?mock_jitter=100-1500 |
mock_ratelimit | rate-limit simulation: allow N requests per 60-second window per client IP, then 429 with Retry-After. Every response (success too) carries x-ratelimit-limit/x-ratelimit-remaining/x-ratelimit-reset headers β test your backoff and countdown UI without getting banned by a real API. Simulated 429s carry x-mockbird-ratelimit: simulated. Parallel workers on one IP (CI runners): add ?mock_ratelimit_key=w1 β each key gets its own counter, like mock_seq_key. Rate-limit guide β | ?mock_ratelimit=5 |
mock_seq | deterministic response sequence: comma-separated statuses served in order, one per request β 503,503,200 means the first request gets a 503, the second a 503, the third (and every later one) the real response. Sticks on the last entry once exhausted. Statuses <400 serve the real response; β₯400 return a simulated error, and failed writes are not applied (the "server" died before processing β safe to point retry logic at). The counter is per project + path + sequence; add mock_seq_key=w1 to give parallel workers isolated counters, and mock_seq_reset=1 to restart (that request counts as #1). Every response carries x-mockbird-seq: pos/len. The deterministic alternative to mock_chaos for retry tests: no flaky randomness β fail exactly twice, then succeed. On the shared demo project, counters are additionally scoped per client IP, so a sequence link shared publicly always starts fresh for each visitor. | ?mock_seq=503,503,200 |
_expand | join the parent object via its <name>Id field | ?_expand=customer |
_embed | attach child records that point back at this record | ?_embed=comments |
select | field projection: return only the named fields (id is always included). Works on single-record GETs too, and can pick _expand/_embed-joined fields. Same syntax as DummyJSON. Alias: _select. Note: a field literally named select can't be used as an exact-match filter. | ?select=title,price |
mock_envelope | wrap the response in your real API's shape: a plain key (data β {"data":[β¦]}) or a URL-encoded JSON template containing "$data". none disables a project default. See response envelopes | ?mock_envelope=data |
mock_cursor | cursor pagination (Stripe/Slack-style): the response becomes {"data":[β¦],"next_cursor":"β¦","has_more":true}. limit is the page size; page is ignored. Follow next_cursor with ?cursor=<token> (implies cursor mode, so mock_cursor=1 is only needed on the first request). Filters, sort, q, select, _expand/_embed, nested routes and snapshot pinning all compose; an invalid token is a 400 β exactly what your infinite-scroll retry logic should see. next_cursor is also sent as the x-mockbird-next-cursor header. Doesn't combine with CSV/streaming modes, and takes precedence over mock_envelope. Note: a field literally named cursor can't be used as an exact-match filter. Infinite-scroll guide β | ?mock_cursor=1&limit=10 |
mock_sse | stream the list as Server-Sent Events β one event per record (event name = resource, incrementing id:), then a final done event. See streaming | ?mock_sse=1 |
mock_stream | stream the list as NDJSON (one record per line, application/x-ndjson). =sse is an alias for mock_sse=1 | ?mock_stream=1 |
mock_stream_interval | ms between streamed records (default 500, 0β5000; total stream time capped at 60 s) | ?mock_stream_interval=200 |
mock_validate | make writes validate like a real API: POST/PUT/PATCH bodies are type-checked against the resource schema β a bad payload gets 422 {"error":"validation_failed","fields":{"price":"expected number, got string"}}. =strict also rejects unknown fields and requires every schema field (PATCH stays partial). =off overrides a project default. See write validation | ?mock_validate=1 |
mock_format | return any list or single-record GET as CSV (RFC 4180) instead of JSON β sending Accept: text/csv works too. Columns = the record's fields (id first); ?select= picks the columns; nested values are JSON-stringified into their cell; filters/sort/pagination apply first. See CSV export | ?mock_format=csv |
Empty-valued params are ignored. ?category=&cursor=&mock_status= is treated as if those params weren't there at all. API clients that import our OpenAPI spec (Hoppscotch, Postman, Insomnia) often pre-fill every documented query param with an empty value β imported requests work as-is instead of filtering the list down to nothing. (Bare flags like ?mock_seq_reset keep working.)
Simulation flags work on every mock endpoint and method β test your spinners and error states without touching your code. See the loading & error states guide for recipes (race conditions, retry loops, Playwright integration).
Chaos note: when mock_chaos injects a failure on a write (POST/PUT/PATCH/DELETE), the write is not applied β exactly like a server that fell over before processing your request. Perfect for verifying your retry logic is safe to re-send. GraphQL requests honor mock_chaos/mock_jitter too (failures come back as an HTTP error with a GraphQL errors array).
Every other mock accepts any payload, so the sad path of your form β the 422, the field errors, the red text under the input β goes untested until the real backend arrives. Mockbird can validate writes against the resource schema you already defined:
# price is a number field β send a string, get a proper 422
curl -X POST 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_validate=1' \
-H 'content-type: application/json' \
-d '{"name":"Widget","price":"cheap"}'
# β 422 {"error":"validation_failed","fields":{"price":"expected number, got string"}}
What's checked per field type: numbers (number/price/rating/β¦) must be JSON numbers, boolean must be a boolean, email/uuid/url/ip/date fields must be well-formed strings, oneOf values must be in the enum. Other string fields just need to be strings.
?mock_validate=1 β type-check the fields present in the body. Unknown extra fields are allowed.?mock_validate=strict β additionally reject unknown fields, and require every schema field on POST/PUT (PATCH remains partial: only what you send is checked).PUT /api/projects/:id/settings with {"validate":"on"} (or "strict") β then your app's normal fetches hit a validating API with no URL changes; ?mock_validate=off overrides per request. Clear with {"validate":"off"}.id is always server-assigned and never validated; nested-route foreign keys are auto-filled and exempt from strict requirements.The response shape is stable β fields maps each bad field to a human-readable reason β so you can build your error-rendering against it. Combine with ?mock_status=500 and ?mock_chaos= for the full sad-path matrix. Full walkthrough: test your form's sad path β
Real APIs rarely return bare arrays. If your backend responds with {"data":[β¦],"total":42} or {"items":[β¦],"meta":{β¦}}, make the mock match β so your frontend's response parsing is exercised for real, and switching to the real API later is a no-op. (mockapi.io sells response reshaping on its paid plan; here it's a query param.)
# per request: a plain key wraps everything under that key
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_envelope=data&limit=2'
β {"data":[{β¦},{β¦}]}
# or a JSON template β "$data" becomes the records; URL-encode it
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?limit=5&page=2&mock_envelope=%7B%22items%22%3A%22%24data%22%2C%22meta%22%3A%7B%22total%22%3A%22%24total%22%2C%22hasMore%22%3A%22%24hasMore%22%7D%7D'
β {"items":[β¦5 recordsβ¦],"meta":{"total":30,"hasMore":true}}
Template placeholders (strings, replaced anywhere in the template): "$data" (the records β required), "$total" (pre-pagination count), "$page", "$limit", "$count" (items on this page), "$hasMore" (boolean).
Project-wide default: set it once and every list and single-record GET is wrapped β no query param needed, so the code you're building never has to know it's talking to a mock:
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/PROJECT/settings \
-H 'x-admin-key: KEY' -H 'content-type: application/json' \
-d '{"envelope": {"data": "$data", "total": "$total"}}'
Rules: applies to successful GETs only β lists (incl. nested routes and snapshot mode), and single records (where $total/$count are 1). Errors, writes (POST/PUT/PATCH/DELETE responses), and GraphQL (which has native shaping) are never wrapped. ?mock_envelope=β¦ overrides the project default per request; ?mock_envelope=none disables it. The X-Total-Count header is still sent either way. Clear the default with {"envelope": null}. Also settable in the dashboard's Response envelope card.
Any list endpoint can stream its records instead of returning one JSON array β test
EventSource clients, fetch-stream readers, progressive rendering and skeleton
screens against a real hosted URL:
# Server-Sent Events: one event per record, then a final `done` event
curl -N 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_sse=1&limit=5&mock_stream_interval=300'
# NDJSON: one record per line
curl -N 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_stream=1&limit=5'
In the browser:
const es = new EventSource("https://mockbird.mockbird.workers.dev/m/demo/products?mock_sse=1&limit=10");
es.addEventListener("products", (e) => render(JSON.parse(e.data))); // event name = resource
es.addEventListener("done", () => es.close()); // {"total":30,"sent":10}
Everything composes: filters, sortBy, pagination, q, select,
nested routes, and snapshot mode all apply before streaming. mock_stream_interval
paces the records (default 500 ms; total stream time is capped at 60 s, so the interval
shrinks automatically for large pages). mock_delay delays the first byte;
mock_status/mock_chaos still win (they return a normal JSON error, handy
for testing EventSource onerror). Envelopes are ignored β streams have their own framing.
Protected-mode note: EventSource can't send an Authorization header, so
protected projects also accept the token as ?mock_token=<jwt> on any mock endpoint
(mock data only; the value is redacted in the request inspector).
Full walkthrough with EventSource + fetch-stream client code: the SSE mocking guide.
_expand / _embed)If a record has a foreign-key-style field like postId or customerId
(the refId field type generates these), you can join related resources in one
request β the same way json-server does it:
# each comment gains a "post" object, looked up from the posts resource via postId
GET /m/abc123/comments?_expand=post
# each post gains a "comments" array of comments whose postId matches
GET /m/abc123/posts/7?_embed=comments
# combine, repeat, or comma-separate for multiple relations
GET /m/abc123/orders?_expand=customer&status=shipped
GET /m/abc123/customers/3?_embed=orders
Works on both list and single-record GETs, alongside filters, sorting and pagination.
_expand=post finds the resource named post, posts (or
-ies plural); _embed matches children by
<singular-parent>Id. Unknown names are ignored, and a dangling id expands
to null β no errors, just data. The blog / ecommerce / saas presets are wired
with these relations out of the box.
Prefer path-style relations? Every parent record exposes its children directly:
# comments whose postId == 1 β same as /m/abc123/comments?postId=1
GET /m/abc123/posts/1/comments
# create a comment with postId=1 set automatically
POST /m/abc123/posts/1/comments
{"body": "Nice post!"}
Nested GETs support everything list endpoints do β filters, search,
sortBy, pagination, even _expand. The parent must exist
(404 otherwise), and the child's <singular-parent>Id field does the
matching. POST fills the foreign key in for you.
Every project publishes a live OpenAPI 3.0 document at
/m/<project>/openapi.json β no auth required. It describes every
resource, field type, query parameter and CRUD operation, so you can import your mock
API straight into Swagger UI, Postman, Insomnia, or generate a typed client with
openapi-generator. The spec updates automatically as you add or change resources.
Every operation carries a clean operationId (listProducts,
createProduct, getProduct, β¦), so codegen tools produce readable
method names β including Apple's
swift-openapi-generator,
which refuses specs without them.
Prefer a native Postman collection? See Postman collection below.
Skip codegen tooling entirely: every project also serves ready-to-paste
TypeScript interfaces at /m/<project>/types.ts
(public, CORS on, no auth β like the OpenAPI spec):
# TypeScript interfaces + Input types for every resource
curl -o api.ts "https://mockbird.mockbird.workers.dev/m/demo/types.ts"
# Zod schemas + z.infer types instead (works with zod v3.20+ and v4)
curl -o schemas.ts "https://mockbird.mockbird.workers.dev/m/demo/types.ts?format=zod"
The TS flavor gives you one interface per resource (fields typed from the
schema β numbers, booleans, and oneOf fields become literal unions like
"low" | "med" | "high") plus a <Name>Input type for POST/PUT
bodies. The Zod flavor gives you <Name>Schema / <Name>ListSchema
with format refinements (.email(), .uuid(), .url(),
.datetime()) and z.infer type exports β drop it into your app and the
data your mock returns is guaranteed to parse. Regenerates on every request, so it always
matches your current resources.
Every project also serves a ready-to-import Postman Collection v2.1 at
/m/<project>/postman.json (public, CORS on, no auth). In Postman choose
Import β Link and paste the URL β you get one folder per resource with List /
Get one / Create / Update / Delete requests, real example bodies matching your field
types, the useful query params (pagination, sort, search, _expand,
mock_delayβ¦) pre-filled but disabled, and a {{baseUrl}}
collection variable. Works in Insomnia and Hoppscotch too (both import Postman format).
# Import from link in Postman β or download it:
curl -o demo.postman_collection.json "https://mockbird.mockbird.workers.dev/m/demo/postman.json"
If the project is in protected mode, the collection gains an
auth (mock) folder whose Login request stores the JWT in a {{token}}
variable via a test script β every other request then authenticates automatically.
Re-import any time to sync with your current schema.
Every project also serves a ready-to-run .http request file at
/m/<project>/requests.http (public, CORS on, no auth β schema only, no
records). Save it and click Send Request:
curl -o requests.http "https://HOST/m/demo/requests.http"
The file works in the VS Code REST Client extension
(humao.rest-client), the JetBrains HTTP Client built into
IntelliJ / WebStorm / PyCharm, and Thunder Client β anything that speaks
the .http format. You get full CRUD per resource with example bodies matching
your field types, a @baseUrl variable, simulation examples
(mock_status, mock_delay) and a GraphQL request. In
protected mode the file starts with a named login request that later
requests reference via {{login.response.body.$.token}} (VS Code REST Client
syntax). Re-download any time to sync with your current schema. Full .http guide β
Every project also serves GraphQL at
/m/<project>/graphql β same records as the REST API, no extra setup.
Open that URL in a browser and you get an embedded GraphiQL IDE with
autocomplete and schema docs (full introspection is on, so Apollo/urql codegen and
Postman work too).
curl https://mockbird.mockbird.workers.dev/m/demo/graphql \
-H 'content-type: application/json' \
-d '{"query":"{ products(limit: 2, sortBy: \"price\", order: \"desc\") { id name price reviews { rating } } }"}'
What the schema gives you, generated from your resources:
| Field | What it does |
|---|---|
products(limit, page, sortBy, order, q, where) | list with pagination, sort, search, and typed exact-match filters (where: {category: "books"}) |
product(id: 3) | single record |
productsCount(where, q) | total matching (ignores pagination) |
| relation fields | review.product (via productId) and product.reviews (children) β same conventions as _expand/_embed |
createProduct(input), updateProduct(id, input), deleteProduct(id) | mutations β they write the same records REST serves, and fire your webhooks |
_raw | the full stored record as JSON (handy for db.json imports with nested data) |
GET works too: /m/demo/graphql?query={productsCount}. Requests share the
project's daily cap; queries are limited to depth 8 and 8 KB. mock_delay
is honored as a query parameter on the endpoint URL.
Every project serves a WebSocket endpoint at
wss://mockbird.mockbird.workers.dev/m/<project>/ws. Out of the box it's an echo
server β any message you send (text or binary) comes straight back β so it's a
drop-in for the dead echo endpoints old tutorials still point at
(echo.websocket.events and friends). Add ?quiet=1 to suppress
the welcome frame for byte-exact echo tests.
npx wscat -c "wss://mockbird.mockbird.workers.dev/m/demo/ws"
> hello
< hello
The part nobody else has: subscribe to your own resources and the records stream back as timed events β a mock realtime feed (ticker, notifications, order stream) with your schema, no backend:
# auto-subscribe from the URLβ¦
npx wscat -c "wss://mockbird.mockbird.workers.dev/m/demo/ws?subscribe=products&interval=500&limit=5"
# β¦or send a command on any open connection:
> {"subscribe":"products","interval":500,"limit":5,"repeat":true,"jitter":300}
< {"type":"subscribed","resource":"products","count":5,"interval":500,"repeat":true}
< {"type":"record","resource":"products","seq":1,"of":5,"data":{"id":1,...}}
< ...
< {"type":"complete","resource":"products","count":5,"repeat":true}
| Option | What it does |
|---|---|
interval | ms between records (100β10000, default 1000) |
jitter | adds 0βjitter random ms per record β irregular, realistic pacing (max 5000) |
limit | records per cycle (max 100) |
repeat | loop the feed until you disconnect (true/1) |
{"unsubscribe":true} | stop the stream, keep the connection |
{"ping":1} | β {"type":"pong","ts":β¦} |
In protected mode pass ?token=<jwt> on the URL
(the browser WebSocket API can't send an Authorization header). Limits: a
connection counts as one request against the daily cap; sessions close after 5 minutes
(reconnect any time); 1000 messages per connection; 64 KB max echo.
Prefer EventSource to WebSockets? Every project also serves
GET /m/<project>/sse (text/event-stream, CORS on). Bare
/sse emits timed tick events β the live test signal every SSE
tutorial needs β and ?subscribe=<resource> streams that resource's
own records as record events, same options as the WebSocket feed
(interval, limit, repeat, jitter;
?quiet=1 skips the welcome event):
curl -N "https://mockbird.mockbird.workers.dev/m/demo/sse?subscribe=products&interval=500&limit=3"
const es = new EventSource("https://mockbird.mockbird.workers.dev/m/demo/sse?subscribe=products&interval=500");
es.addEventListener("record", (e) => {
const { seq, of, data } = JSON.parse(e.data); // data = the record itself
console.log(`${seq}/${of}`, data);
});
es.addEventListener("complete", () => es.close()); // omit with &repeat=1
Streams are reconnect-aware: every event carries an id:,
so when EventSource auto-reconnects it sends Last-Event-ID and the stream
resumes at the next position instead of starting over (you can also pass
?lastEventId=N by hand). In protected mode pass
?token=<jwt> (EventSource can't send headers either). Limits: one
daily-cap request per connection; sessions close cleanly after 5 minutes with a
bye event (EventSource reconnects on its own; retry: 3000 is
pre-set); 1000 events per connection.
Already have an OpenAPI 3.x or Swagger 2.0 spec? Post it (JSON or YAML) and get a
live mock of your real API's shape β resources found from your REST paths, field types
inferred from schemas (formats like email, uuid,
date-time are honored), enums keep your exact values, and
everything is seeded with realistic fake data:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @openapi.yaml
# β {"id":"abc123","adminKey":"...","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "dashboard":"https://mockbird.mockbird.workers.dev/app#open=abc123:KEY",
# "resources":[{"name":"pets","seeded":20,"url":"https://mockbird.mockbird.workers.dev/m/abc123/pets"}, ...],
# "warnings":[]}
Options: ?seed=50 records per resource (max 100), ?name=my-mock
project name (defaults to the spec's info.title). Or use the
Import button in the dashboard β /app#import
opens the panel directly β and paste or upload the file. Notes:
/users, /users/{id}, nested /users/{id}/posts β posts). $ref and allOf are resolved; wrapper objects like {"data": [...]} are unwrapped.price, avatar, city, createdAt, *Idβ¦ become matching realistic generators.example / examples (media-level, schema-level, or Swagger 2.0 response.examples), those records are hosted verbatim instead of faker-seeded β the mock answers with exactly what your spec promises, Prism-style, but stateful (writes persist, filters/pagination work). Wrapper shapes like {"data": [...]} are unwrapped; a bare object example becomes a 1-record collection; a collection-path example beats a single-record one. Pass ?examples=0 to ignore spec examples and seed fake data instead.warnings)./search, /health, RPC-style verbs like POST /invoices/{id}/send are served as fixed routes answering with the spec's own example (verbatim, any content type) or a value generated from the response schema (uuid/date-time/enum aware). {param} segments become :param. Created routes are listed under routes in the response and editable in the dashboard. A spec with zero CRUD-shaped paths still imports as a pure fixed-route mock./m/<project>/openapi.json) re-import losslessly.Coming from json-server? POST your db.json to the same
endpoint and Mockbird hosts it β your exact records, not fake data. Each
top-level key becomes a resource; numeric ids are preserved; nested
objects/arrays inside records are kept verbatim. Filtering, pagination, nested routes,
_expand/_embed, webhooks and the request inspector all work on
your data immediately:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @db.json
# β {"id":"abc123","kind":"db.json","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "resources":[{"name":"posts","records":42,"url":"https://mockbird.mockbird.workers.dev/m/abc123/posts"}, ...]}
Notes:
paths/openapi/swagger is treated as a spec, anything shaped like {"posts": [...]} as a db. YAML works too.ids are renumbered to integers (listed in warnings); singular objects (json-server's profile) become 1-record collections.And it round-trips β eject anytime. GET /m/<project>/db.json
returns your entire live dataset in json-server's native format (data only, so the
file works verbatim). No lock-in:
curl -o db.json https://mockbird.mockbird.workers.dev/m/demo/db.json
npx json-server db.json # the same API, running locally
Prefer in-process mocking? GET /m/<project>/msw.js
returns a self-contained Mock Service Worker (MSW v2) handlers
module with your current data baked in β CRUD, exact filters, q search,
_sort/_order, _page/_limit + X-Total-Count, all in memory.
Drop it into setupServer(...handlers) (Node tests) or
setupWorker(...handlers) (browser) and your mock keeps working offline with zero
Mockbird dependency. Relations, operator suffixes, chaos/latency and GraphQL stay hosted-only β
they compose:
curl -o msw.js https://mockbird.mockbird.workers.dev/m/demo/msw.js
# import { setupServer } from 'msw/node'
# import { handlers } from './msw.js'
# setupServer(...handlers).listen()
Don't have a db.json? Any JSON array of objects works. POST it to the same
endpoint and it becomes a live collection β your records verbatim, integer ids
preserved (missing ones filled in), field types inferred from the data. Name the collection
with ?resource= (default items):
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?resource=todos" \
--data-binary @todos.json
# todos.json = [{"id":1,"title":"buy milk","done":false}, ...]
# β {"baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123", "resources":[{"name":"todos","records":3,...}]}
You instantly get GET/POST/PUT/PATCH/DELETE, filters (?done=true,
_gte/_lte/_ne/_like), pagination, search, select=, GraphQL and every
export on that data β a full read-write API, not just a hosted file. A plain JSON
object works too: object-of-collections is treated as a db.json; singular object
values become 1-record collections (handy for config blobs β GET /m/<id>/config/1).
Moving off Postman mock servers? POST an exported Postman Collection v2.x
to the same endpoint. Requests become resources (GET /users,
/users/:id β users) and your saved example responses become
the hosted records, verbatim β wrappers like {"data": [...]} are
unwrapped, duplicate ids deduplicated. Resources without saved examples fall back to a
schema inferred from raw JSON request bodies and get seeded fake data
(?seed=N):
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @collection.json
# β {"id":"abc123","kind":"postman","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "resources":[{"name":"users","records":12,...},{"name":"orders","seeded":20,...}]}
Notes:
:id, {{userId}}, numbers and UUIDs are treated as parameters; a leading {{baseUrl}} and /api/v1-style prefixes are ignored.warnings) β in Postman, save a response as an example and re-export./m/<project>/postman.json) re-import cleanly (built-in auth/graphql folders are skipped).Open DevTools on any page of your app, use it for a minute, then Network tab β Export HAR. POST that file to the same endpoint and the JSON API traffic the page actually received becomes a hosted mock β the recorded response bodies are served back verbatim as records. It's record & replay without a proxy: your frontend can now run against yesterday's real data, offline backends and all.
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @myapp.har
# β {"id":"abc123","kind":"har","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "resources":[{"name":"products","records":34,...},{"name":"cart-items","records":3,...}]}
Notes:
/api/v2/users?page=1 and /api/v2/users/42 both land in a users collection; numeric/UUID segments are treated as parameters; repeated responses from polling are deduplicated (by id when present).{"data": [...]} are unwrapped. Endpoints that only appear as JSON writes (a POST with a body but no useful response) get a schema inferred from the request body and seeded fake data instead.text/plain still counts.The same endpoint accepts VCR (Ruby) and vcrpy (Python) cassettes β the YAML
files those libraries record (http_interactions: / interactions:; the JSON
serializers work too). The 2xx JSON response bodies recorded in the cassette become hosted
records, served back verbatim β your cassette suite's data, now behind a URL that curl,
Postman, a browser, or a teammate's machine can hit (cassette replay is normally visible
only inside the recording process).
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import --data-binary @cassettes/checkout.yml
# β {"id":"abc123","kind":"cassette","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123", ...}
Notes:
{"data": [...]} wrappers unwrap, base64 bodies decode).Authorization / API-key headers into cassettes verbatim by default β the import reads only URLs and JSON bodies, and warns you if credential headers were present so you know to rotate them if the file was ever shared.POST a CSV or TSV file to the same endpoint and every row becomes a
record. The header row names the fields, the delimiter (comma, semicolon, or tab) is
auto-detected, quoted values with embedded commas/newlines work (RFC 4180), and
values are typed per column: whole-number columns become numbers,
true/false columns become booleans, zero-padded codes like
01234 stay strings, and an id column keeps your ids.
curl -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?resource=people' \
-H 'content-type: text/csv' --data-binary @people.csv
# β {"id":"abc123","kind":"csv","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "resources":[{"name":"people","records":124,...}]}
Notes:
?resource=<name> names the collection (default items); ?name= names the project. In the dashboard (/app#import) the file name is used automatically.?format=csv or a content-type: text/csv header. Headers with spaces are sanitized (First Name β First_Name); empty cells import as null; cells containing JSON (["a","b"]) are parsed._page/_limit, q search, select=, GraphQL, OpenAPI/types.ts exports, and db.json to eject. Full walkthrough + SheetDB/Sheety comparison: CSV β REST API guide.Every list and single-record GET can come back as CSV instead of JSON β
add ?mock_format=csv or send Accept: text/csv. Filters, sort,
search, pagination and ?select= all apply first, so a filtered download is one URL
(the response carries a content-disposition filename, so a browser visit saves
products.csv):
# the whole (paged) list as a spreadsheet
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&limit=100'
# only some columns, sorted β ?select= picks the CSV columns
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price&sortBy=price&order=desc'
# via content negotiation
curl -H 'accept: text/csv' 'https://mockbird.mockbird.workers.dev/m/demo/products'
Rules: RFC 4180 output (CRLF rows, quotes doubled, fields quoted when they contain
commas/quotes/newlines). Columns are the union of the returned records' fields with
id hoisted first; nested objects/arrays are JSON-stringified into their cell.
Works on nested routes and snapshot mode too. Envelope and streaming params don't combine
with CSV, and writes/GraphQL are JSON-only. Together with CSV import this
is a full round-trip: spreadsheet in β live API β spreadsheet out.
When defining a resource you give each field a type. Available types:
id uuid firstName lastName fullName username email avatar image
word words title sentence paragraph number price percent boolean
date pastDate futureDate url domain ip phone city country address
zipCode company jobTitle color latitude longitude rating age slug
status category refId
refId generates foreign-key-style ids (1β20, matching the default seed
range) β name the field postId, userId, etc. and it plugs straight
into _expand / _embed.
Every project gets a built-in placeholder image endpoint, so your mock data ships with images that actually render β no third-party image service involved. Try every parameter live in the playground β
GET /m/<project>/img/300x200 # SVG, shows "300Γ200"
GET /m/<project>/img/300 # square shorthand
GET /m/<project>/img/640x480?text=Product+7
GET /m/<project>/img/300x200?bg=1e3a8a&fg=fff
GET /m/<project>/img/128?round=1&text=OS&seed=olivia # avatar-style circle
GET /m/<project>/img/300/200 # picsum.photos-shaped path β same image
GET /m/<project>/img/seed/abc/300/200 # picsum seed path (= ?seed=abc)
GET /m/<project>/img/300x200.png # PNG output (also ?format=png)
GET /m/<project>/img/seed/abc/300/200.jpg # .jpg/.jpeg paths work too (serve PNG bytes)
GET /m/<project>/img/400x300?grayscale # picsum-style effects β¦
GET /m/<project>/img/400x300?blur=5 # β¦ work on SVG and PNG output alike
text (β€64 chars), bg/fg (3- or 6-digit hex, no #), seed (deterministic palette color), round=1 (circle)..png suffix or ?format=png for raster output β same layout, colors and determinism as the SVG, useful where SVG doesn't render (email clients, OG images, PDF generators, some native image pipelines). .jpg/.jpeg suffixes are accepted for drop-in compatibility with picsum-style URLs and serve PNG bytes (every consumer of a placeholder accepts PNG). PNG is capped at 1,000,000 pixels (e.g. 1000Γ1000) β use the default SVG for larger sizes. Text renders in a deliberately blocky bitmap font. When raster is the requirement (email HTML, og:image cards, native mobile image views), see the PNG placeholder guide.?grayscale (bare flag; ?greyscale works too) and ?blur=1-10 (bare ?blur = 5) β the same params picsum.photos uses, on /img and /avatar (all styles), SVG and PNG output alike, deterministic. Useful for testing blur-up lazy-loading placeholders and muted "disabled" states. On PNG output ?blur is capped at 262,144 pixels (512Γ512) β raster blurring is CPU-hungry; the default SVG blurs at any size.Cache-Control: public. Counts toward the project's daily request cap.image and avatar fields point at these endpoints automatically, so a fresh ecommerce preset renders product grids with zero setup.<img> tags can't send Authorization headers.There's also a dedicated avatar endpoint with ui-avatars.com-compatible params (swapping the host is a drop-in):
GET /m/<project>/avatar?name=John+Doe # 128px square, "JD"
GET /m/<project>/avatar/64?name=Ada+Lovelace&rounded=true
GET /m/<project>/avatar?name=John+Doe&background=0D8ABC&color=fff&rounded=true
GET /m/<project>/avatar/150?u=a042581f4e29 # pravatar-style unique id
name (initials source), size 1β1000 (or path), background/bg, color/fg, rounded/round, length 1β3, uppercase=false, seed/u.?style=identicon renders a GitHub-style 5Γ5 mirrored identicon instead of initials; ?style=shapes renders an abstract geometric avatar. Both are deterministic per seed/name and compose with size, rounded, background/color. Guide: free identicon API.?format=png (ui-avatars-compatible) or a .png path suffix (/avatar/128.png) returns PNG with identical layout and colors β all three styles included.avatar fields use this endpoint. Guide: free avatar placeholder API.Every project also gets a dynamic Open Graph card endpoint β an
og:image URL you can paste into a <meta> tag today, no image editor,
no deploy. It's PNG by default because that's what social scrapers require
(Facebook, Twitter/X, Slack, Discord and Bluesky don't render SVG cards):
GET /m/<project>/og?title=My+launch+post # 1200Γ630 PNG
GET /m/<project>/og?title=Hello&subtitle=shipped+today&site=example.com
GET /m/<project>/og?title=Dark+or+light&theme=light
GET /m/<project>/og?title=Brand+colors&bg=0b1220&accent=22d3ee
GET /m/<project>/og?title=With+a+mark&logo=myapp # identicon logo, bottom-right
GET /m/<project>/og/800x418?title=Other+sizes # /og/:WxH override
GET /m/<project>/og.svg?title=Vector+preview # SVG twin (?format=svg too)
title (β€120 chars, wrapped up to 4 lines), subtitle/desc (β€200, up to 3 muted lines), site (small footer text with an accent square), logo=<seed> (deterministic 5Γ5 identicon mark, bottom-right), theme=dark|light, bg/fg/accent (hex, no #), seed (picks the accent deterministically)./og/:WxH overrides (120β2000 per side; PNG capped at 1,000,000 px β the default fits). Same URL β same image forever, Cache-Control: public./img β distinctive and legible in feeds; the SVG twin uses a real system font for previews. Honest note: for brand-perfect typography use a real renderer like @vercel/og β this is the zero-setup placeholder that unblocks you today.og:image tags, side-project launch cards, blog-post defaults (?title={{post.title}} from your templating), link-preview testing without deploying image infra.@vercel/og and card SaaS): free Open Graph image API.Every project also serves scannable QR codes from a URL β no key, no library,
no signup. PNG by default (what <img> tags, print stylesheets and most tooling
want); SVG twin on request:
GET /m/<project>/qr?data=https%3A%2F%2Fexample.com # 256Γ256 PNG
GET /m/<project>/qr/500?data=hello # /qr/:size override (64β2000)
GET /m/<project>/qr.svg?data=hello # vector twin (?format=svg too)
GET /m/<project>/qr?data=hello&ecc=H&margin=2 # error correction + quiet zone
GET /m/<project>/qr?data=WIFI%3AT%3AWPA%3BS%3Amynet%3BP%3Asecret%3B%3B # wifi payload
data (required, β€1000 chars, UTF-8; aliases text/url), size 64β2000 (or /qr/:size path; PNG capped at 1,000,000 px β 1000Β² fits, use SVG beyond), ecc=L|M|Q|H (error-correction level, default M), margin=0-16 (quiet-zone modules, default 4 β the spec minimum for reliable scanning), fg/bg (hex, no #).Cache-Control: public. Counts toward the project's daily request cap.ecc survives more damage but packs less data (byte-mode capacity tops out at 1,273 bytes at H, 2,953 at L β 1000 ASCII chars fit at every level, but multi-byte UTF-8 text can overflow; the error tells you to drop to a lower level or shorten).<img> tags can't send Authorization headers.Every project also serves chart images from numbers in the URL β no key, no chart library, no build step. PNG by default (README/markdown embeds and chat previews want raster); SVG twin on request:
GET /m/<project>/chart?data=3,7,4,9,6 # 600Γ300 line chart PNG
GET /m/<project>/chart?data=12,19,7,24&type=bar&labels=q1,q2,q3,q4&title=Signups
GET /m/<project>/chart/300x80?data=3,7,4,9,6,2,8&type=spark # README sparkline
GET /m/<project>/chart.svg?data=3,7,4,9,6&type=area # vector twin (?format=svg too)
GET /m/<project>/chart?data=3,7,4&theme=dark&color=34d399 # dark theme + custom series color
data (required; comma-separated numbers, 1β300 points, β€4000 chars; alias values), type=line|area|bar|spark (default line; spark is a compact axis-free line+fill for README sparklines, with a dot on the last point), labels=a,b,c (x-axis labels β count must match the data; auto-thinned when they'd overlap), title (β€60), min/max (y-range override; auto-scaled with 5% padding otherwise β bars always anchor at 0), color (series hex, no #; alias fg), bg (hex or transparent), theme=light|dark, grid=0 (hide gridlines), points=1 (dots on every data point)./chart/:WxH overrides (40β2000 Γ 20β2000; PNG capped at 1,000,000 px β use SVG beyond). Same URL β same bytes forever, Cache-Control: public. Counts toward the project's daily request cap./img; the SVG twin uses a real system font. One data series per chart today.<img> tags can't send Authorization headers.One-click resource schemas: users, posts, products, todos, comments, orders, reviews, customers, events. Pick one in the dashboard or pass {"template":"products"} to the management API.
Instead of adding resources one by one, create a project from a preset and get a full multi-resource fake backend instantly:
postId), authorsproductId / customerId)In the dashboard, pick a preset next to "New project". Via API: POST /api/projects with {"name":"shop","preset":"ecommerce"} β the response lists every created endpoint. Reference fields use the refId type: a plausible foreign key in the 1β20 range so joins against the default seed data just work. One-click shortcut: opening /app#new=ecommerce (or #new=blog, #new=saas) creates an anonymous project with that preset and drops you straight into its dashboard.
No preset? New projects still work instantly: a plain POST /api/projects (or bare /app#new) includes a seeded starter resource items β 20 fake product-ish records, live at /m/<id>/items the moment the create call returns. Reshape or delete it, or pass {"blank": true} (dashboard: pick "empty project") to start with nothing.
Every project keeps a log of its last 50 requests β method, path, query string, response status, Origin header, (for writes) the request body, and captured request headers (content-type, user-agent, accept, referer, and every x-* header β so webhook signatures like x-hub-signature-256 are right there; authorization is logged with its value redacted). See it in the dashboard under Recent requests (tick βliveβ to watch requests stream in, click a row to expand headers + full body), or fetch it from the management API:
curl https://mockbird.mockbird.workers.dev/api/projects/abc123/requests -H 'x-admin-key: KEY'
Great for answering βis my app actually calling the API, and with what?β β wrong paths, missing bodies and CORS origins show up instantly. Combined with a catch-all custom route it doubles as a request bin (a free webhook.site alternative β see the guide). The shared demo playground's log is public, no key needed: https://mockbird.mockbird.workers.dev/api/projects/demo/requests.
The same window is available as a standard HAR 1.2 file β the format DevTools and every HTTP-debugging tool already speak:
curl -o trace.har 'https://mockbird.mockbird.workers.dev/api/projects/abc123/requests.har' -H 'x-admin-key: KEY'
# or in the dashboard: Recent requests β β.har ββ
Open it in Chrome or Firefox DevTools (Network tab β Import HAR), a HAR viewer, or diff two runs with jq. It takes the same trajectory filters as the JSON inspector, so requests.har?method=POST&since=<episode-start> exports exactly one agent episode's writes β handy for attaching an eval run's traffic to a bug report or review. Honest caveats: entries are what your client sent plus the response status β the inspector doesn't store response bodies β and request bodies over 2 KB are truncated. Works on share links too (/api/share/<token>/requests.har), and the demo's is public: /api/projects/demo/requests.har. (The reverse direction β turning a DevTools-recorded HAR into a mock β is HAR import.)
The inspector accepts filters, and every response includes count β the number of matching requests in the retained window β so a test or eval grader can assert on behavior, not just final state:
| Param | Meaning |
|---|---|
method=DELETE | HTTP method; comma-list ok (PUT,PATCH,DELETE) |
path=/tasks | segment-aware path prefix β matches /tasks and /tasks/5, not /tasksomething |
status=404 | exact response status; comma-list ok |
status_gte=400 / status_lte= | status range (status_gte=400 = any error) |
since=<epoch ms | ISO-8601> | only requests at/after that time β record the episode start, assert about only that episode |
limit=N | truncate the returned array (count still reflects all matches) |
# the agent must not have deleted anything:
curl -s '.../api/projects/ID/requests?method=DELETE' -H 'x-admin-key: KEY' | jq -e '.count == 0'
# no request ever errored:
curl -s '.../api/projects/ID/requests?status_gte=400' -H 'x-admin-key: KEY' | jq -e '.count == 0'
# it DID write exactly one order:
curl -s '.../api/projects/ID/requests?method=POST&path=/orders' -H 'x-admin-key: KEY' | jq -e '.count == 1'
Honest caveat: filters apply within the retained window (window: 50 is echoed in the response). For eval runs, fork per run β a fresh fork's log contains exactly one episode's traffic, so the window is never a problem. The same filters work on share-link inspector URLs (/api/share/<token>/requests?method=DELETE) and on the MCP inspect_requests tool.
Mint a read-only share link for a project and hand it to anyone who should see it but not touch it β a teammate reviewing your mock, a client checking the API shape, or (if you're an AI agent) the human supervising your sandbox:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/share -H 'x-admin-key: KEY'
# → {"shareUrl": "https://mockbird.mockbird.workers.dev/share/<token>", ...}
Anyone opening the shareUrl in a browser gets a live read-only view: resources with record counts, a data browser (works even when the project is in protected mode), custom routes, snapshots, exports, and the request inspector β refreshable, so they can watch traffic arrive. No writes are possible through the link and the admin key never appears. For scripts there's a JSON flavor: GET /api/share/<token> (overview), /api/share/<token>/requests (plus /requests.har β the HAR export), /api/share/<token>/data/<resource>?page=&limit= β and /api/share/<token>/verdict/<name> runs a saved verdict keylessly (grading a run is a read).
One link per project. POST .../share with {"rotate":true} invalidates the old link and mints a fresh one; DELETE .../share revokes it; GET .../share shows the current link. Also available in the dashboard (Share card) and over MCP as the share_project tool.
Point a webhook at your app and Mockbird fires a signed POST every time a record is created, updated or deleted in your project β perfect for building and testing webhook consumers before the real event source exists. Set it in the dashboard (Webhook card) or via API:
# set (or update) the webhook β returns the signing secret
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/abc123/webhook \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"url":"https://your-app.example.com/hooks/mockbird","events":["created","deleted"]}'
# fire a test delivery right now
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/webhook/test -H 'x-admin-key: KEY'
# see the last 20 delivery attempts (status, error, duration)
curl https://mockbird.mockbird.workers.dev/api/projects/abc123/webhook/deliveries -H 'x-admin-key: KEY'
Each delivery is a JSON POST like:
{
"id": "evt_1a2b3c4d",
"event": "products.created", // <resource>.<created|updated|deleted>, or "test"
"project": "abc123",
"resource": "products",
"action": "created",
"record": { "id": 31, "name": "..." }, // deletes include the removed record
"ts": 1753430000000
}
Headers: X-Mockbird-Event, X-Mockbird-Delivery (event id), and X-Mockbird-Signature: sha256=<hex> β an HMAC-SHA256 of the raw body with your signing secret, so you can practice real signature verification:
// Node.js
const crypto = require('node:crypto');
function verify(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return signatureHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
Details: 5-second timeout, redirects are not followed, no retries, 100 deliveries per project per day. Non-2xx responses and errors are visible in the delivery log. URLs must be public http(s) β localhost and private IPs are rejected (use a tunnel like cloudflared or ngrok to reach your dev machine).
Every project has built-in auth endpoints so you can build real login flows against a fake backend: login forms, token storage, Authorization headers, 401 redirects, token-expiry refresh logic β all testable before your real auth exists.
# any email + password logs in (it's a mock) β a real signed JWT comes back
curl -X POST https://mockbird.mockbird.workers.dev/m/abc123/auth/login \
-H 'content-type: application/json' \
-d '{"email":"dev@example.com","password":"anything"}'
# β {"token":"eyJhbGciOi...","tokenType":"Bearer","expiresIn":3600,"user":{...}}
# who am I? (validates signature + expiry)
curl https://mockbird.mockbird.workers.dev/m/abc123/auth/me -H "Authorization: Bearer $TOKEN"
# register works too β if the project has a users resource, it creates a real record
curl -X POST https://mockbird.mockbird.workers.dev/m/abc123/auth/register \
-H 'content-type: application/json' \
-d '{"email":"new@example.com","password":"x","name":"New Person"}'
The tokens are genuine HS256 JWTs (signed with a per-project secret): they carry iat/exp claims, verify, and expire for real. Pass "expiresIn": 5 at login to get a token that dies in 5 seconds β the easiest way to test your app's expiry handling (min 5s, max 7 days, default 1 hour).
Users are real data. If the project has a users (or customers/accounts/members) resource, logging in with a record's email returns that record as the user (password-ish fields stripped), and /auth/register inserts a new record β visible instantly via REST and GraphQL. No users resource? A user object is synthesized from the email.
Flip Protected mode in the dashboard (Mock auth card) β or via API β and every endpoint of the project (REST, nested routes, GraphQL) starts requiring Authorization: Bearer <token>, returning proper 401s with WWW-Authenticate otherwise. Your auth guards, interceptors and redirect-to-login logic get a realistic workout:
# toggle via API
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/abc123/settings \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"authMode":"protected"}' # or "none"
# now unauthenticated requests fail like production would
curl -i https://mockbird.mockbird.workers.dev/m/abc123/products # β 401 {"error":"missing bearer token", ...}
The /auth/* endpoints and openapi.json stay open (so you can always log in), and the exported OpenAPI spec gains a bearerAuth security scheme automatically. Expired or tampered tokens get 401 token expired / invalid signature β exactly what your interceptor needs to see. Full walkthrough with a client-side interceptor example: mock JWT auth guide.
Save a named copy of a project's entire data state β every record in every resource β then restore it whenever you want. This turns a mock project into a deterministic test fixture: your tests can create/update/delete freely, and one call puts everything back exactly as it was.
# save the current state as "baseline"
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"name":"baseline"}'
# ...your test suite trashes the data via the mock API...
# put it all back β records, ids, everything, exactly as saved
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots/baseline/restore \
-H 'x-admin-key: KEY'
# list / inspect / delete
curl https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots -H 'x-admin-key: KEY'
curl https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots/baseline -H 'x-admin-key: KEY' # includes full data
curl -X DELETE https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots/baseline -H 'x-admin-key: KEY'
Typical uses: a beforeEach/globalSetup hook in Playwright or Cypress that restores baseline so every run starts identical; an empty-state snapshot (resources with zero records) vs a full one for demoing both UI states; a known dataset for reproducing a bug report. Saving with an existing name overwrites it. Restore also rebuilds resources you deleted since the snapshot. Snapshots don't fire webhooks. Limits: 10 snapshots per project, 1 MB each. The dashboard has a Snapshots card with one-click save/restore. Full Playwright/Cypress walkthrough: deterministic test data guide.
A snapshot doesn't have to be captured from live data β pass a data object and the snapshot is built from the collections you send, exactly as written:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots \
-H 'x-admin-key: KEY' -H 'content-type: application/json' -d '{
"name": "expected",
"data": {
"tasks": [ {"id":1,"title":"write tests","done":true},
{"id":2,"title":"ship it","done":true} ],
"labels": [] }}'
Records are stored verbatim (ids preserved; missing ids get filled in sequentially), an empty array means "this resource, with zero records" β perfect for expected: emptied assertions β and you can name resources that don't exist live yet (restore rebuilds them). Resources that do exist live keep their live field schema, so a restore never changes your types.ts/GraphQL output. This is the natural way to author an eval answer key: write the expected end-state inline, run the agent, then diff against it β no need to mutate live data into the expected shape first just to capture it. Same 64 KB management-API body limit applies; for bigger states, set the data up via import and capture normally.
Snapshots pin reads; forking isolates writes. POST /api/projects/:id/fork copies the whole project β resources, every record verbatim (ids preserved), custom routes, and behavior settings (protected mode, envelope, validation) β into a brand-new project with its own id and adminKey:
# fork your project into an independent copy
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/fork \
-H 'x-admin-key: KEY' -H 'content-type: application/json' -d '{"name":"run-42"}'
# β {"id":"xyz789","adminKey":"...","forkedFrom":"abc123","baseUrl":".../m/xyz789", ...}
# the demo can be forked by anyone, no auth β the demo dataset as your own project
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/demo/fork
Typical use: parallel test workers or AI-agent eval runs that all need to write. Keep one template project, fork it per run (each run gets a private URL β no shared-state races, no restore coordination), and DELETE /api/projects/:id the fork when done β or skip the cleanup entirely with a ttl. Not copied: webhooks, snapshots, the request log, and (for demo forks) the demo's showcase routes. Forks count toward your project limits like any create. Mock-auth note: tokens are signed per-project, so JWTs issued by the source project are rejected by the fork β log in against the fork's own /auth/login.
ttl)Pass "ttl": <seconds> (or ?ttl=) on create, import, or fork and the project deletes itself β all records, routes, snapshots, logs β when the time is up. Range 60 s to 7 days. Built for CI and agent-eval runs: fork with a ttl and a crashed run can't leak sandboxes; there is no cleanup step to forget.
# a sandbox that cleans up after itself, even if your CI dies mid-run
curl -X POST 'https://mockbird.mockbird.workers.dev/api/projects/demo/fork?ttl=900'
# β { ..., "expiresAt":"2026-09-09T17:00:00.000Z", "expiresIn":900 }
# changed your mind β keep it (or push the deadline back)
curl -X PUT .../api/projects/xyz789/settings -H 'x-admin-key: KEY' -H 'content-type: application/json' -d '{"ttl": null}' # keep forever
# ... -d '{"ttl": 3600}' # new deadline: 1h from now
Semantics: expiresAt is returned on create and shown on project GET (with a live expiresIn countdown). Expiry is enforced immediately on the REST mock endpoints and the management API (requests return 404 and the data is deleted on the spot); idle expired projects are fully swept within ~30 minutes by a background job. ttl in settings is always counted from now β sending {"ttl": 3600} twice moves the deadline, it doesn't stack. Projects without a ttl behave exactly as before: they never expire.
GET /api/projects/:id/snapshots/:name/diff compares a snapshot (expected) with the project's current data (actual) and returns a structured verdict β no dump-both-and-eyeball:
curl "https://mockbird.mockbird.workers.dev/api/projects/abc123/snapshots/expected/diff" \
-H 'x-admin-key: KEY'
# β {"identical": false,
# "summary": {"resourcesAdded":[], "resourcesRemoved":[],
# "records": {"added":1, "removed":1, "changed":2, "unchanged":22}},
# "resources": [{"resource":"tasks", "added":[6], "removed":[2],
# "changed":[{"id":3,"fields":{"done":{"expected":false,"actual":true}}}], "unchanged":2}]}
# one-liner pass/fail (also sent as the x-mockbird-identical response header)
curl -s ".../snapshots/expected/diff" -H 'x-admin-key: KEY' | jq -e '.identical'
Semantics: added = records present now but not in the snapshot; removed = in the snapshot but gone now; changed lists per-field {expected, actual} pairs (canonical comparison β key order never causes a false diff). Options: ?ignore=updatedAt,createdAt excludes volatile field names from comparison everywhere; ?against=other-snapshot compares two snapshots instead of snapshot-vs-live. Resource-level drift shows up as resourcesAdded/resourcesRemoved and schemaChanged. Detail lists cap at 200 ids / 100 changed records per resource (counts stay exact; changedTruncated: true flags the cap).
Built for eval/CI grading: save the expected end-state as a snapshot once, run the agent or test against a fork, then diff and assert identical β the grader checks state, not the transcript. Recipe: AI-agent sandbox guide.
POST /api/projects/:id/verdict composes the state check (snapshot diff) and any number of trajectory constraints on the request log into a single {pass, checks[]}:
curl -s https://mockbird.mockbird.workers.dev/api/projects/abc123/verdict \
-H 'x-admin-key: KEY' -H 'content-type: application/json' -d '{
"snapshot": "expected",
"ignore": ["updatedAt"],
"trajectory": [
{"method": "DELETE", "count": 0},
{"method": "POST", "path": "/orders", "count": 1},
{"status_gte": 400, "count": 0}
]
}'
# snapshot: live data must match it Β· ignore: volatile fields excluded from the state check
# trajectory: never deleted / exactly one order created / nothing errored
# β {"pass": true, "checks": [
# {"kind":"state","snapshot":"expected","pass":true,"summary":{...}},
# {"kind":"trajectory","filter":{"method":"DELETE"},"expect":{"count":0},"count":0,"pass":true},
# ...], "window": 50}
# one-liner gate (pass is also sent as the x-mockbird-pass response header)
curl -s .../verdict -H 'x-admin-key: KEY' -d '{"snapshot":"expected"}' | jq -e '.pass'
Both parts are optional β send only snapshot for a pure state grade, or only trajectory for pure behavior constraints. Each trajectory constraint takes the same filters as the request-inspector (method comma-list, path segment-aware prefix, status, status_gte/status_lte, since) plus an expectation: count (exact), min and/or max (max 20 constraints). Failed checks carry a human-readable detail; the state check includes the full diff resources breakdown.
Want the HTTP status itself to be the gate? Add "failStatus": 422 and a failing verdict returns that status instead of 200 β so curl -sf, a Playwright expect(res.ok), or any http-client error path is the CI gate, no JSON parsing needed. Trajectory counts see the retained window (last 50 requests) β fork per run so the log is exactly one episode. Over MCP: the verdict tool.
Sending the whole spec on every run means every grader script carries the answer key. Instead, save the spec once, by name, on the project:
# author the grading spec on your TEMPLATE project (max 10 per project)
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/abc123/verdicts/final \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"snapshot":"expected","trajectory":[{"status_gte":400,"count":0}],"failStatus":422}'
# run it by name (admin)
curl -s .../api/projects/abc123/verdict -H 'x-admin-key: KEY' -d '{"name":"final"}'
# GET /verdicts lists them; GET/DELETE /verdicts/final reads/removes one
Saved verdicts are copied by fork (pass {"withSnapshots":true} too if the spec references a snapshot), and β the payoff β they run keylessly from the share link:
# the grader/CI needs ONLY the share token and the name β no admin key, no spec
curl -sf https://mockbird.mockbird.workers.dev/api/share/<token>/verdict/final
That completes the eval-harness handoff: the agent under test gets the fork's mock URL (can't read the grading spec, which lives behind the admin API), the grader gets the share link and a name, and with failStatus set, curl -sf on that URL is the whole CI gate. Running a check is a pure read β it can't mutate data, so handing out the link risks nothing. The share page also shows saved checks as one-click Run buttons for human reviewers. Over MCP: verdict with name.
Every saved verdict also renders as a live SVG badge β append /badge.svg to the keyless run URL and embed it in a README, PR description, or dashboard:

Each render runs the verdict against the live data + request log β green pass / red fail, no stale build artifacts. The badge is always HTTP 200 (a red badge still has to render through image proxies like GitHub's camo β the curl -sf CI gate stays on the JSON route), caches for ~60s, and takes ?label= to override the left-hand text. Unknown names render a gray unknown badge. The share page shows each check's badge next to its Run button β click it to copy the Markdown.
Any GET request to the REST endpoints β and any GraphQL query β can be answered directly from a saved snapshot β without changing the project's live data β by sending the X-Mockbird-Snapshot header (or ?mock_snapshot= for places where you can't set headers, like an <img> tag or a browser address bar):
# live data
curl https://mockbird.mockbird.workers.dev/m/abc123/products
# same URL, answered from the "empty" snapshot β live data untouched
curl https://mockbird.mockbird.workers.dev/m/abc123/products -H 'X-Mockbird-Snapshot: empty'
# query-param variant
curl "https://mockbird.mockbird.workers.dev/m/abc123/products?mock_snapshot=edge-cases"
# GraphQL queries honor it too (schema + data come from the snapshot)
curl https://mockbird.mockbird.workers.dev/m/abc123/graphql -H 'X-Mockbird-Snapshot: empty' \
-H 'content-type: application/json' -d '{"query":"{ productsCount }"}'
Filters, search, sorting, pagination, _expand/_embed, and nested routes all work against the snapshot's records. This is built for parallel test workers: worker A pins empty, worker B pins edge-cases, worker C uses live data β same project, same URLs, zero interference, no restore-order headaches. Snapshot mode is read-only: REST writes return 405 and GraphQL mutations error (restore the snapshot if you want to mutate that state). No header β live data, exactly as before.
Try it right now on the demo β it ships with two built-in snapshots, empty (every resource, zero records β test your empty states) and edge-cases (hand-crafted nasty records: a 100-char product name, unicode + HTML-ish characters, price 0, price 1999999.99, an empty description, an out-of-stock one-star product, and id 9999 after id 6):
curl "https://mockbird.mockbird.workers.dev/m/demo/products?mock_snapshot=empty" # β []
curl "https://mockbird.mockbird.workers.dev/m/demo/products?mock_snapshot=edge-cases" # β 7 deliberately nasty records
Not everything is CRUD. Define your own endpoints β any method, any path β with a templated response body. Custom routes take precedence over resource routes, so you can also override a generated endpoint with a fixed payload (trailing-* catch-alls are the one exception β they fall back behind your resources). Walkthrough with live no-signup examples: mock any HTTP endpoint.
# a health endpoint
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/routes \
-H 'X-Admin-Key: YOUR_ADMIN_KEY' -H 'content-type: application/json' \
-d '{"path":"/health","body":{"status":"ok","time":"{{now}}"}}'
curl https://mockbird.mockbird.workers.dev/m/abc123/health
# β {"status":"ok","time":"2026-07-25T21:53:18.105Z"}
# an echo endpoint with path params + request data
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/routes \
-H 'X-Admin-Key: YOUR_ADMIN_KEY' -H 'content-type: application/json' \
-d '{"method":"POST","path":"/config/:key","status":201,
"body":"{\"key\":\"{{params.key}}\",\"got\":{{{body}}},\"id\":\"{{uuid}}\"}"}'
curl -X POST "https://mockbird.mockbird.workers.dev/m/abc123/config/theme" \
-H 'content-type: application/json' -d '{"dark":true}'
# β {"key":"theme","got":{"dark":true},"id":"82b14067-β¦"}
Template placeholders, resolved per request:
| placeholder | value |
|---|---|
{{query.x}} | query-string parameter x |
{{params.x}} | path parameter from a :x segment |
{{params.splat}} | the rest of the path matched by a trailing * |
{{body.x}} / {{body.a.b}} | field from the request body β JSON (dot paths work) and HTML form posts (application/x-www-form-urlencoded or multipart/form-data, the default for htmx and plain <form> submits, and what Twilio/Slack-style webhooks send) |
{{headers.x}} | request header (case-insensitive; cookie, x-admin-key and client-IP infra headers are never exposed to templates) |
{{method}} / {{path}} | request method / full path |
{{now}} / {{ts}} | ISO timestamp / epoch millis |
{{uuid}} / {{rand}} | random UUID / random integer |
Double braces are JSON-string-escaped β safe to drop inside "quotes" even when the value contains quotes. Triple braces ({{{body.count}}}, {{{body}}}) insert the raw JSON value β numbers stay unquoted, objects inline. Missing values render as an empty string (or null in triple braces).
Options per route: method (GET/POST/PUT/PATCH/DELETE/ANY), status (200β599 β make /legacy always 410, or a /teapot 418), contentType (default JSON β serve text/plain, XML, CSVβ¦), headers (up to 10 extra response headers, templated too), delayMs (built-in latency up to 5 s). Same method+path saves overwrite. ?mock_delay= and ?mock_status= still work on custom routes, protected mode guards them, and hits show up in the request inspector.
Serving HTML: set contentType: "text/html" and a browser navigation renders your route as a real page β mock whole HTML fragments for htmx frontends or host a tiny test page next to its mock API. For safety on this shared origin, every custom-route response carries Content-Security-Policy: sandbox allow-scripts allow-forms allow-popups allow-modals: scripts in your mocked page run normally and can fetch() your mock endpoints (CORS is open), but the page gets an opaque origin β it can never read Mockbird cookies or localStorage. This header can't be overridden.
A trailing * segment matches any remaining path (available as {{params.splat}}). Unlike exact and :param routes, catch-alls are a fallback: your resource endpoints and more specific routes always win, and the * route picks up everything else. One more carve-out: a plain GET on the bare project root serves the project index β but POST/PUT/etc. to the root still land in your catch-all, so webhook receivers keep working.
# turn a project into a request bin: catch every method on every path
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/routes \
-H 'X-Admin-Key: YOUR_ADMIN_KEY' -H 'content-type: application/json' \
-d '{"method":"ANY","path":"/*","body":{"ok":true,"caught":"{{method}} /{{params.splat}}"}}'
curl -X POST https://mockbird.mockbird.workers.dev/m/abc123/anything/at/all -d '{"hello":1}' -H 'content-type: application/json'
# β {"ok":true,"caught":"POST /anything/at/all"} β¦and the request β headers,
# body, everything β is now in the request inspector
Point a webhook sender at https://mockbird.mockbird.workers.dev/m/abc123/hooks, watch deliveries (with their x-* signature headers) arrive in the inspector, and reply with whatever status/body the sender expects. That's the webhook.site workflow, with no 100-request cap and no 7-day expiry. Scoped bins like /webhooks/* work too, and the catch-all coexists with your mocked resources.
# manage
GET /api/projects/:id/routes # list
POST /api/projects/:id/routes # create or overwrite
DELETE /api/projects/:id/routes/:routeId # remove
Point a project at your real API and Mockbird becomes a partial mock: requests that match one of your resources, custom routes or built-in endpoints are served from the mock; everything else is forwarded to the upstream β method, path, query string, request body and Authorization header included β and the real response comes back. It's Mirage's passthrough / Mockoon's proxy mode, as a hosted URL your whole team (and CI) can share.
# turn it on (admin key required; null clears it)
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/abc123/settings \
-H 'X-Admin-Key: YOUR_ADMIN_KEY' -H 'content-type: application/json' \
-d '{"proxyBase":"https://api.example.com/v1"}'
curl https://mockbird.mockbird.workers.dev/m/abc123/products # your resource β mock data
curl https://mockbird.mockbird.workers.dev/m/abc123/payments/42 # no such resource β GET https://api.example.com/v1/payments/42
Useful when the endpoint you need to fake doesn't exist yet (or misbehaves) but the rest of the API is fine β mock the one endpoint, proxy the rest, change one base URL in your app.
/auth, /graphql, /img, exports) are never proxied. Record-level 404s on a mocked resource stay local β a defined resource is authoritative.x-mockbird-proxied: <upstream-host> and appear in the request inspector like everything else.?mock_status=503, ?mock_chaos=0.3 and ?mock_delay=2000 work on proxied paths without the request ever reaching the real API β drill your retry logic against a production API safely. All mock_* parameters are stripped before forwarding.Accept, Content-Type, Authorization, conditional-request headers, and your custom x-* headers (never X-Admin-Key). Upstream must be a public https/http host; responses over 2 MB or slower than 10 s return a 502. Proxied requests count against the project's daily cap.Flip on recording and every proxied 2xx response is saved as a custom route β the next request to that method + path is served locally, byte-for-byte, without touching the upstream. Browse your app against the mock URL once, and the endpoints you exercised become stubs you can keep, edit, or delete. (This is WireMock's record mode / Mockoon's "record from proxy", hosted and free.)
curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/abc123/settings \
-H 'X-Admin-Key: YOUR_ADMIN_KEY' -H 'content-type: application/json' \
-d '{"proxyRecord":true}'
curl https://mockbird.mockbird.workers.dev/m/abc123/payments/42 # proxied β real API, response saved (x-mockbird-recorded: 1)
curl https://mockbird.mockbird.workers.dev/m/abc123/payments/42 # replayed locally β no x-mockbird-proxied header, upstream never hit
DELETE /api/projects/:id/routes/:routeId) to re-record. Recorded routes are marked βΊ in the dashboard and "recorded": true in GET /api/projects/:id/routes.{{template}} rendering entirely, so upstream payloads containing braces are safe.?mock_status=503, ?mock_chaos, ?mock_delay.Retry-After when exceeded); the shared demo playground gets 50,000Heads-up for Python stdlib users: the hosting platform's edge protection blocks the default Python-urllib/x.y User-Agent with a 403 (this is platform-wide, not a Mockbird rule). Fix: send any User-Agent header, e.g. urllib.request.Request(url, headers={"User-Agent": "my-app"}). The requests library, curl, fetch, axios, and Go are unaffected.
Everything the dashboard does is also an API. Create a project, then use its adminKey via X-Admin-Key header:
# create project (no preset = includes a seeded starter resource "items",
# so the API serves data immediately; pass {"blank": true} to start empty)
curl -X POST https://mockbird.mockbird.workers.dev/api/projects -H 'content-type: application/json' -d '{"name":"shop"}'
# β {"id":"abc123","adminKey":"...","baseUrl":"https://mockbird.mockbird.workers.dev/m/abc123",
# "dashboard":"https://mockbird.mockbird.workers.dev/app#open=abc123:KEY","resources":[{"name":"items",...}],"try":"curl ..."}
# add a resource from a template, seeded with 50 records
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/resources \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"name":"products","template":"products","seed":50}'
# or with custom fields
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123/resources \
-H 'content-type: application/json' -H 'x-admin-key: KEY' \
-d '{"name":"gyms","fields":[{"name":"name","type":"company"},{"name":"city","type":"city"},{"name":"rating","type":"rating"}],"seed":25}'
# x-admin-key, "Authorization: Bearer KEY", or ?key=KEY all work
The create response's dashboard URL (/app#open=<id>:<adminKey>)
opens the project in the web dashboard on any machine β data browser/editor,
request inspector, snapshots, webhooks β and remembers it in that browser for next time.
Made the project from a script or an AI agent? That link is the hand-off. It embeds the admin
key (in the URL fragment, which never reaches a server or a log) β share it only with the
project's owner.
Mockbird is API-first β everything works without a browser, and AI agents are welcome as users. Two machine-friendly entry points:
GET /api β JSON index of every endpoint, with a copy-paste quickstart/llms.txt β plain-markdown guide for LLM-based tools and agentsPer-project OpenAPI 3.0 specs (/m/<project>/openapi.json) are public, so code
generators and agents can introspect any mock API they have the URL for.
Mockbird is also a hosted MCP server β point Claude Code, Cursor, VS Code, or any
MCP client at https://mockbird.mockbird.workers.dev/mcp and your coding agent can spin up and drive mock
backends as tools: create projects, import an OpenAPI spec / db.json / Postman collection / CSV,
add seeded resources, query and write records, define custom routes, save/restore snapshots.
No signup, no API key, no OAuth β it just works (Streamable HTTP transport, stateless).
# Claude Code
claude mcp add --transport http mockbird https://mockbird.mockbird.workers.dev/mcp
# Cursor / Windsurf / generic JSON config (mcp.json)
{ "mcpServers": { "mockbird": { "url": "https://mockbird.mockbird.workers.dev/mcp" } } }
# VS Code
code --add-mcp '{"name":"mockbird","type":"http","url":"https://mockbird.mockbird.workers.dev/mcp"}'
Eighteen tools: create_project, import_data,
fork_project (copy a template project per eval/CI run β pass ttl for a
self-expiring fork that cleans itself up even when the run crashes;
withSnapshots carries answer keys along), add_resource,
project_info, query_records, write_record,
generate_fake_data (instant realistic fake data β ready-made shapes like persons /
products / credit cards or a custom field map, deterministic with a seed, stateless, no project
needed), custom_route, snapshots (save / restore / diff β action:"diff"
grades an eval run against an expected snapshot), verdict (the whole eval grade in
one call β state diff + trajectory constraints β {pass, checks[]}, see verdict), inspect_requests (read the
request inspector β verify what your app or webhook sender actually sent),
share_project (mint a read-only share link β hand your human reviewer a browser URL to inspect the sandbox, no key handover),
delete_project (clean up short-lived test projects),
check_api_status (live health of the ~37 public mock/testing APIs on our
status tracker β check whether httpbin or JSONPlaceholder is down
before your agent relies on it), uptime_monitor (free downtime alerts for
any public URL β checked every 30 minutes, webhook on down/recovery, public status page +
badge), heartbeat (a dead man's switch for cron jobs and recurring agent runs β
the job pings Mockbird; a missed ping fires the alert webhook), and
watch_service_status (subscribe a webhook to down/recovered events for any
tracked public API β or omit the webhook to get a pollable subscription your agent
checks on its own schedule). They call the exact same API as the
dashboard and curl, so the request inspector, daily caps, and anonymous per-IP limits all apply;
the project adminKey returned by create_project flows through tool
arguments. A typical agent session: βcreate an ecommerce mock and point my frontend's
.env at itβ β one tool call, done. The mock API itself stays plain HTTP, so the
code your agent writes ships with a working URL, not a mock library.
It also ships MCP prompts β ready-made workflows your client surfaces as
slash commands: mock_an_api (plain-English description β live API),
mock_from_spec (paste an OpenAPI spec / db.json / Postman collection / HAR / CSV),
simulate_failures (point retry/timeout handling at a mock that misbehaves on demand),
monitor_uptime (arm downtime alerts for a URL or a cron job in one go),
and is_it_down (live status of a public mock API + a drop-in replacement if it's
failing). In Claude Code that's e.g. /mockbird:mock_an_api (MCP) from the slash-command
picker.
The server also exposes MCP resources: mockbird://docs/llms.txt
(the full agent-oriented API guide) and mockbird://api/index (machine-readable JSON
endpoint index) β readable via resources/list + resources/read, so a
connected agent never needs a separate HTTP fetch to learn the API. A discovery manifest lives at
/.well-known/mcp.json.
Prefer raw JSON-RPC? POST /mcp with initialize /
tools/list / tools/call messages works from curl too.
Full walkthrough with a sample agent session: the MCP guide.