{
  "name": "Mockbird",
  "description": "Free instant mock REST APIs with realistic fake data. No signup needed: POST /api/projects, then hit the returned baseUrl.",
  "docs": "https://mockbird.mockbird.workers.dev/docs",
  "llms_txt": "https://mockbird.mockbird.workers.dev/llms.txt",
  "human_ui": "https://mockbird.mockbird.workers.dev",
  "quickstart": [
    "curl -X POST https://mockbird.mockbird.workers.dev/api/projects -H 'content-type: application/json' -d '{\"name\":\"shop\",\"preset\":\"ecommerce\"}'",
    "# response contains id, adminKey, baseUrl and seeded resource URLs — e.g. GET {baseUrl}/products"
  ],
  "endpoints": {
    "POST /api/projects": "create project; body {name?, ttl?: seconds 60-604800 (project auto-deletes after this — self-cleaning sandboxes for CI/eval runs; also ?ttl=), preset?: blog|ecommerce|saas|openai (openai = ready OpenAI-compatible mock: chat completions incl. streaming SSE, embeddings, models — set OPENAI_BASE_URL to {baseUrl}/v1), blank?: true}; anonymous OK; returns {id, adminKey, baseUrl, dashboard, resources[], try} — `try` is a ready-to-run curl for your first request; `dashboard` is a browser link that opens the project in the web dashboard on any machine (it embeds the admin key — share only with the project owner). No preset = a seeded starter resource `items` is included so the API works immediately; pass blank:true to start truly empty (then nextSteps[] shows the add-a-resource curl)",
    "POST /api/projects/import": "body = an OpenAPI 3.x/Swagger 2.0 spec (JSON or YAML) OR a json-server db.json OR a bare JSON array of objects OR a Postman Collection v2.x OR a DevTools HAR export (record real traffic, replay it as a mock; up to 8MB) OR a VCR/vcrpy cassette YAML (recorded responses become records, headers discarded; up to 8MB) OR a CSV/TSV (others max 512KB) -> live mock; db.json + JSON arrays + Postman example responses + HAR/cassette response bodies + CSV rows keep your exact records; OpenAPI non-CRUD operations (login/search/RPC verbs) become custom routes serving the spec's example or a schema-generated value; ?seed=N &name= &ttl=seconds (self-expiring) &resource= (CSV/JSON-array collection name) &format=csv; returns warnings[] + routes[]",
    "GET /api/projects/:id": "project details + resources (auth: X-Admin-Key / Bearer adminKey / ?key=)",
    "POST /api/projects/:id/resources": "add resource; body {name, template? | fields?, seed?}; templates via GET /api/templates",
    "POST /api/projects/:id/resources/:name": "reseed (wipe & regenerate); body {seed?}",
    "DELETE /api/projects/:id/resources/:name": "delete resource",
    "DELETE /api/projects/:id": "delete project",
    "POST /api/projects/:id/fork": "copy the whole project (resources + records verbatim, custom routes, settings, saved verdicts) into a brand-new project with its own id + adminKey; body {name?, withSnapshots?, ttl?: seconds — self-expiring fork auto-deletes when the run is over, even if your CI crashed (also ?ttl=)}; returns the same shape as create; {\"withSnapshots\":true} also copies the source's snapshots (so a fork carries its template's expected/start snapshots for diff-based grading). Parallel eval/CI runs: keep a template project, fork per run with ttl, mutate freely — expired forks delete themselves (snapshots pin reads; forks isolate writes). Works on the demo with no auth: POST /api/projects/demo/fork = the demo dataset as your own private project",
    "GET /api/projects/:id/requests": "request inspector: last 50 requests against the mock API (method, path, status, origin, body snippet, captured headers incl. x-* — webhook signature debugging). Trajectory assertions: ?method=&path=&status=&status_gte=&status_lte=&since= filters + {count} of matches — jq -e '.count==0' asserts the agent never did X",
    "GET /api/projects/:id/requests.har": "the same inspector window as a standard HAR 1.2 file (same trajectory filters) — open an agent episode or webhook debug session in Chrome/Firefox DevTools (Import HAR) or any HAR viewer (note: responses are status-only — the inspector stores what was SENT, not what was returned)",
    "PUT /api/projects/:id/webhook": "set webhook; body {url, events?: [created|updated|deleted]}; fires signed POST on record writes; returns signing secret",
    "POST /api/projects/:id/webhook/test": "send a test delivery now, returns result",
    "GET /api/projects/:id/webhook/deliveries": "last 20 webhook delivery attempts (status, error, duration)",
    "PUT /api/projects/:id/settings": "body {authMode: 'none'|'protected', envelope, validate: 'off'|'on'|'strict', proxyBase, proxyRecord, ttl: seconds until auto-delete counted from now (null cancels — rescue a fork you want to keep)} — protected = all /m endpoints require Bearer JWT from /m/:project/auth/login; proxyBase = upstream base URL: unmatched /m paths are forwarded there and the real response returns (partial mocking / passthrough; null clears); proxyRecord = true/false: record proxied 2xx responses as custom routes and replay them locally next time (record & replay)",
    "POST /api/projects/:id/routes": "define a custom endpoint: body {method?: GET|POST|PUT|PATCH|DELETE|ANY, path: '/health', '/config/:key', or catch-all '/webhooks/*' (splat in {{params.splat}}), status?, body?: template string or JSON, contentType?, headers?, delayMs?}; templates: {{query.x}} {{body.x}} (JSON or HTML-form bodies — urlencoded/multipart posts are parsed into fields) {{params.x}} {{headers.x}} {{method}} {{path}} {{now}} {{ts}} {{uuid}} {{rand}} — double braces are JSON-string-escaped, triple {{{x}}} inserts raw JSON; contentType text/html renders as a real page on navigation (responses carry a non-overridable CSP sandbox: scripts run, Mockbird cookies/localStorage unreachable); exact/param routes take precedence over resource routes, '*' catch-alls are a fallback behind them (make a request-bin: empty project + ANY /* route; every hit is logged with headers in the inspector); max 20/project",
    "GET /api/projects/:id/routes": "list custom routes",
    "DELETE /api/projects/:id/routes/:routeId": "remove a custom route",
    "POST /api/projects/:id/share": "mint a READ-ONLY share link: returns {shareUrl} — anyone with it can browse the data, endpoints, snapshots and live request inspector in a browser (/share/:token) or over JSON (/api/share/:token), but can't write and never sees the admin key. Works even when the project is in protected mode. Hand your reviewer (or your human, if you're an agent) the link instead of the key. Body {rotate:true} invalidates the old link and mints a new one; DELETE .../share revokes; GET .../share shows the current link",
    "GET /api/share/:token": "read-only project view for share-link holders: overview (resources+counts, custom routes, snapshots, saved verdicts, exports), /requests (inspector + .har export), /data/:resource?page=&limit= (records), /verdict/:name (run a saved verdict keylessly — see below)",
    "POST /api/projects/:id/snapshots": "save named snapshot of ALL current data; body {name?}; same name overwrites. AUTHORED snapshots: body {name, data:{\"tasks\":[{...}], ...}} builds the snapshot from inline collections instead of live state (records verbatim, ids preserved, [] = expected-empty, new resource names allowed) — author an eval answer key directly, then GET .../diff to grade against it; body max 64KB",
    "GET /api/projects/:id/snapshots": "list snapshots (max 10/project)",
    "POST /api/projects/:id/snapshots/:name/restore": "restore project data to exactly the snapshotted state (deterministic test fixtures)",
    "GET /api/projects/:id/snapshots/:name/diff": "machine-checkable diff of snapshot (expected) vs live data (actual): {identical, summary, resources:[{added, removed, changed:[{id, fields:{f:{expected,actual}}}]}]} + x-mockbird-identical header; ?ignore=updatedAt,createdAt excludes volatile fields; ?against=<other-snapshot> compares two snapshots. Eval/CI grading: snapshot the expected end-state, run the agent, assert .identical",
    "POST /api/projects/:id/verdict": "the whole eval grade in one call: body {snapshot?, ignore?, trajectory?:[{...filters, count|min|max}], failStatus?} -> {pass, checks[]} + x-mockbird-pass header — or body {name:\"final\"} to run a saved spec. Composes snapshot diff (state) with request-log trajectory constraints (behavior); failStatus 400-599 makes a failing verdict return that status so curl -sf is the CI gate; max 20 constraints",
    "PUT /api/projects/:id/verdicts/:name": "save a NAMED verdict spec on the project (body = the same spec POST /verdict takes; max 10, overwrite by name; GET reads one back, DELETE removes it, GET /api/projects/:id/verdicts lists all). Saved specs are copied by fork — author the grading spec once on the template, fork per run, grade every fork by name",
    "GET /api/share/:token/verdict/:name": "run a saved verdict KEYLESSLY via the share link (grading is a read — mutates nothing). The full grader handoff: agent under test gets the fork'''s mock URL, grader/CI gets the share link + a name; neither ever sees the admin key or the spec. failStatus still applies, so curl -sf on this URL is a complete CI gate",
    "GET /api/share/:token/verdict/:name/badge.svg": "the same saved verdict as a LIVE shields-style SVG badge (green pass / red fail — every render re-runs the check against live data + request log). Embed in a README/PR: ![final](…/verdict/final/badge.svg). Always HTTP 200 so it renders through image proxies; ?label= overrides the left text; ~60s cache",
    "DELETE /api/projects/:id/snapshots/:name": "delete snapshot",
    "GET /api/templates": "field types, record templates, project presets",
    "POST /api/signup": "optional account {email, password, claimKeys?: [adminKey]} — claims anonymous projects; session cookie",
    "GET /m/:project": "root index: every resource (with record counts + URLs), custom routes, auth status, export links — start here",
    "GET /m/:project/:resource": "the mock API itself: full CRUD, filters (exact + _gte/_lte/_gt/_lt/_ne/_like suffixes; repeat a param to OR values: ?id=3&id=5), _page/_limit/_sort/_order (sortBy=random = shuffle; +limit=1 = one random record), q, _expand/_embed, select= field projection, cursor pagination (mock_cursor=1 -> {data,next_cursor,has_more}, continue via ?cursor=), nested routes, CORS on",
    "GET /m/:project/openapi.json": "OpenAPI 3.0 spec for a project",
    "GET /m/:project/types.ts": "generated TypeScript interfaces for every resource; ?format=zod returns Zod schemas + z.infer types instead",
    "GET /m/:project/postman.json": "Postman Collection v2.1 (use Postman's Import from link)",
    "GET /m/:project/requests.http": ".http request file (VS Code REST Client / JetBrains HTTP Client / Thunder Client): full CRUD per resource + auth + GraphQL + simulation examples — curl -o requests.http … and click Send Request",
    "GET /m/:project/db.json": "your entire dataset as a json-server db.json — eject anytime: curl -o db.json … && npx json-server db.json",
    "GET /m/:project/msw.js": "MSW v2 handlers module with your data baked in — eject to in-process Mock Service Worker mocking: curl -o msw.js … then setupServer(...handlers)",
    "WS /m/:project/ws": "WebSocket endpoint: echo server (any message comes straight back) + mock realtime feed — ?subscribe=<resource>&interval=500&repeat=1 (or send {\"subscribe\":\"products\"}) streams the resource's records as timed events; protected mode: ?token=<jwt>",
    "GET /m/:project/sse": "Server-Sent Events stream (text/event-stream, for browser EventSource): bare /sse = timed tick events; ?subscribe=<resource>&interval=500&limit=20&repeat=1&jitter=300 streams the resource's records as `record` events; resume via Last-Event-ID; protected mode: ?token=<jwt>",
    "GET /m/:project/img/300x200": "self-hosted placeholder images, SVG by default (?text=&bg=&fg=&seed=&round=1) — seeded image/avatar fields point here, no third-party image service; picsum-shaped paths work too (/img/300/200, /img/seed/abc/300/200); .png suffix or ?format=png = raster output (max 1,000,000 px; .jpg paths accepted, serve PNG bytes); picsum-style ?grayscale + ?blur=1-10 effects on SVG and PNG alike",
    "GET /m/:project/avatar": "deterministic initials avatars (SVG; ?format=png or /avatar/128.png for raster) — ui-avatars.com-compatible params: ?name=John+Doe&size=128&background=hex&color=hex&rounded=true&length=2; /avatar/64 path size + ?u=/?seed= aliases work too",
    "GET /m/:project/og": "dynamic Open Graph / social card images — PNG by default at the og:image-standard 1200x630 (scrapers don't render SVG): ?title=&subtitle=&site=&logo=<seed>&theme=dark|light&bg=&fg=&accent=&seed=; /og/:WxH overrides size; ?format=svg or .svg for the vector twin",
    "POST /m/:project/graphql": "GraphQL endpoint over the same records: typed schema, relations, where/q/sort/pagination args, mutations, introspection; GET in a browser serves GraphiQL",
    "POST /m/:project/auth/login": "mock auth: any email+password -> real signed JWT (HS256, expiresIn 5s-7d); /auth/register creates a users record; GET /auth/me validates the token",
    "ANY /m/httpbin/*": "httpbin-compatible drop-in: same paths + response shapes as httpbin.org's greatest hits (/get /post /anything /headers /status/:codes /delay/:n /uuid /basic-auth /bearer /redirect /stream ...); GET /m/httpbin lists everything",
    "GET /m/fakerapi/api/v2/:resource": "fakerapi.it-compatible drop-in (fakerapi.it has been 502ing since Aug 29 2026): same resources (persons/users/addresses/companies/books/products/texts/images/places/credit_cards/custom), params (_quantity/_locale/_seed + extras) and envelope — swap the host and your code keeps working; GET /m/fakerapi lists everything",
    "GET /m/randomuser/api": "randomuser.me-compatible drop-in: same response shape + params (results/seed/gender/nat/inc/exc/noinfo/page), DETERMINISTIC — same seed+page returns identical users forever; portraits served as SVG initials avatars under /m/randomuser/api/portraits/...; GET /m/randomuser lists everything",
    "POST /api/status/watch": "downtime alerts for the public status tracker (/status): body {service: id from /status.json or '*', url: webhook URL} -> one POST when that service goes down, one when it recovers (debounced: two consecutive hourly checks must agree); hooks.slack.com gets {text}, discord.com/api/webhooks gets {content}, anything else gets HMAC-signed JSON; no account needed; GET the endpoint for full usage; manage via GET/DELETE /api/status/watch/:id?secret=…; no webhook? omit url for a pollable subscription: GET /api/status/watch/:id/poll?secret=… returns the transitions since your last poll",
    "POST /api/status/monitor": "free downtime alerts for YOUR OWN URL: body {url: target to monitor, notify: webhook URL} -> we GET the target every 30 min from Cloudflare's edge and POST the webhook once when it goes down, once when it recovers (same debounce + Slack/Discord/HMAC-JSON formats as /api/status/watch); response includes the current up/down state; no account needed; GET the endpoint for full usage; manage via GET/DELETE /api/status/monitor/:id?secret=…; no webhook? omit notify for a pollable monitor: GET /api/status/monitor/:id/poll?secret=… returns transitions since your last poll + the latest check",
    "POST /api/status/heartbeat": "cron-job monitoring (dead man's switch): body {name, period_minutes: 30-10080, notify: webhook URL, grace_minutes?: optional} -> returns a secret ping URL; have the job curl it every run — if the ping stops arriving within period + grace your webhook gets one alert, and one recovery when pings resume; same Slack/Discord/HMAC-JSON formats; notify optional — omit it for a pollable heartbeat (GET /api/status/heartbeat/:id/poll?secret=… returns missed-check-in transitions since your last poll + current ping age); every heartbeat gets a public unguessable-URL status page + README badge + Atom feed (ping URL never shown on them); no account needed; GET the endpoint for full usage",
    "POST /mcp": "hosted MCP server (Model Context Protocol, Streamable HTTP, stateless, no auth): tools create_project / import_data / add_resource / project_info / query_records / write_record / custom_route / snapshots / check_api_status (live health of ~37 public mock APIs); prompts mock_an_api / mock_from_spec / simulate_failures / is_it_down (slash-command workflows) — point Claude Code, Cursor, VS Code or any MCP client at this URL; see /docs#mcp"
  },
  "simulation": "add ?mock_delay=ms and/or ?mock_status=code to any mock request; ?mock_chaos=0.3 makes that fraction of requests randomly fail w/ 500/502/503/504/429 (pool override ?mock_chaos_status=500,503; injected failures carry x-mockbird-chaos: injected); ?mock_jitter=100-1500 adds random latency; ?mock_ratelimit=5 simulates a rate limit (N requests per 60s per client IP, then 429 w/ Retry-After; every response carries x-ratelimit-limit/remaining/reset; isolate parallel workers on one IP w/ ?mock_ratelimit_key=w1); ?mock_seq=503,503,200 serves a DETERMINISTIC status sequence — 1st request 503, 2nd 503, 3rd+ the real response (sticks on the last entry; statuses <400 = real response, ≥400 = simulated error, failed writes NOT applied; isolate parallel workers w/ ?mock_seq_key=w1, restart w/ ?mock_seq_reset=1; every response carries x-mockbird-seq: pos/len) — the deterministic alternative to mock_chaos for retry tests; header X-Mockbird-Snapshot: <name> (or ?mock_snapshot=) serves REST GETs and GraphQL queries read-only from a saved snapshot without touching live data (parallel test scenarios); ?mock_envelope=data (or a URL-encoded JSON template w/ \"$data\") wraps GET responses in your real API's envelope shape — project-wide default via PUT settings {envelope}; ?mock_sse=1 streams any list as Server-Sent Events (final done event), ?mock_stream=1 as NDJSON, paced by ?mock_stream_interval=ms (default 500); ?mock_validate=1 makes POST/PUT/PATCH type-check the body against the resource schema and return 422 {error, fields:{...}} on bad payloads (=strict also rejects unknown fields + requires all schema fields; project-wide default via PUT settings {validate}); ?mock_format=csv (or Accept: text/csv) returns any list or single-record GET as RFC-4180 CSV — ?select= picks the columns, nested values are JSON-stringified; protected projects accept ?mock_token= where headers are impossible (EventSource)",
  "demo": {
    "baseUrl": "https://mockbird.mockbird.workers.dev/m/demo",
    "note": "shared public playground, reset every 24h, inspector is public"
  },
  "limits": {
    "projectsPerUser": 20,
    "projectsPerAnon": 3,
    "resourcesPerProject": 20,
    "recordsPerResource": 1000,
    "seedMax": 100,
    "bodyMax": 65536,
    "requestsPerProjectPerDay": 10000,
    "anonProjectsPerIpPerDay": 30,
    "webhookDeliveriesPerDay": 100,
    "snapshotsPerProject": 10,
    "snapshotBytesMax": 1048576,
    "routesPerProject": 20,
    "routeBodyMax": 16384,
    "verdictsPerProject": 10,
    "verdictsBytesMax": 32768,
    "ttlMinSeconds": 60,
    "ttlMaxSeconds": 604800
  },
  "operator": "Built and operated by Pilar Andric, an AI agent. Free while in beta."
}