Real-looking data at real URLs, like every other fake API. The difference: you decide how it behaves. Force a 503, add two seconds of latency, make a third of requests fail — on any endpoint, from the query string.
Free, no signup to read, and a key takes one request if you want a higher limit.
Coming from JSONPlaceholder? Same resources, same fields — here is the one-line migration.
Press Send.
They work on every endpoint, including nested routes and sandboxes. Combine them freely.
# a slow endpoint — test your loading state GET /v1/posts?_delay=3000 # a hard failure — test your error boundary GET /v1/posts?_status=503 # an intermittent one — test your retry logic GET /v1/posts?_fail_rate=0.3 # slow AND flaky, because that is what production is GET /v1/products?_delay=1500&_fail_rate=0.2
The three above are a coin toss, a permanent outage, and a wait. None of them can test retry logic or a circuit breaker, because both need a sequence: fail, fail, then work — so a test can assert which attempt recovered rather than that one eventually did.
curl -X POST https://flakyapi.dev/v1/scenario -d '{"fail":2}' # → {"id":"66b6…"} GET /v1/posts?_scenario=66b6… # 503 · x-scenario-attempt: 1 GET /v1/posts?_scenario=66b6… # 503 · x-scenario-attempt: 2 GET /v1/posts?_scenario=66b6… # 200 — your backoff worked # rewind between tests, without spending an attempt POST /v1/scenario/66b6…/reset
A rate limit does not fail first and recover — it is fine, fine, fine, then
429, and it stays that way. So does a quota, a free trial, and an
access token that expires mid-run. Send succeed instead of
fail and the sequence inverts.
curl -X POST https://flakyapi.dev/v1/scenario -d '{"succeed":3}' # → {"id":"9f2a…"} status defaults to 429 here GET /v1/posts?_scenario=9f2a… # 200 · x-scenario-remaining-successes: 2 GET /v1/posts?_scenario=9f2a… # 200 GET /v1/posts?_scenario=9f2a… # 200 — allowance spent GET /v1/posts?_scenario=9f2a… # 429 · Retry-After: 1 — and stays 429
This is the one you cannot get from a real provider without burning real quota or waiting out a real window. It never recovers, because a quota that ran out does not refill halfway through your test.
Both directions are what people currently write a throwaway fake server for, and together they are the one control no other mock API has at all.
# a 429 that actually says when to come back GET /v1/posts?_status=429 # Retry-After: 5 GET /v1/posts?_status=503&_retry_after=30 # a body that stops mid-record — the .json() catch path nobody tests GET /v1/posts?_malformed=1 # the CORS error, on purpose. only a browser will refuse it GET /v1/posts?_cors=off
Every resource answers at the same kinds of URL, and the method decides what happens there. Writes are echoed back, not stored, unless you use a sandbox.
| URL | Method | What it does |
|---|---|---|
| Loading… | ||
| Resource | Rows | Nested |
|---|---|---|
| Loading… | ||
GET /v1/todos?userId=3&completed=true # filter by any field GET /v1/posts?_q=voluptate # full-text search GET /v1/products?_sort=price&_order=desc # sort GET /v1/photos?_page=2&_limit=50 # paginate GET /v1/photos?_start=40&_limit=10 # or offset, json-server style GET /v1/products?_select=title,price # only these fields GET /v1/posts/1/comments # nested
POST, PUT, PATCH and DELETE are echoed, not stored — same as every other
mock API, and the response says so in an x-mock-write header so nobody
loses an afternoon to it. When you need writes that persist, take a sandbox.
# 1. a free key (no confirmation email) curl -X POST https://flaky.dev/v1/keys \ -H 'content-type: application/json' \ -d '{"email":"you@example.com"}' # 2. a sandbox — your own writable copy, 24 hours curl -X POST https://flaky.dev/v1/sandbox \ -H 'authorization: Bearer flk_...' # 3. write to it. this one is real. curl -X POST https://flaky.dev/v1/sandbox/<id>/posts \ -H 'content-type: application/json' \ -d '{"title":"this persists"}'
A sandbox is an overlay, not a copy — only records you change are stored, so the shared dataset stays cached at the edge and your sandbox stays fast.
Everything above works with no key and no signup. A key is not a gate — it raises the daily limit tenfold and unlocks sandboxes. One request, no confirmation email, no password, nothing to click.
curl -X POST https://flakyapi.dev/v1/keys \ -H 'content-type: application/json' \ -d '{"email":"you@example.com"}' # → {"key":"flk_...", "tier":"free"} — use it straight away
Send it on any request and the tier changes:
curl https://flakyapi.dev/v1/posts \
-H 'authorization: Bearer flk_...'
The address is stored only so the key can be revoked or you can be contacted about it. Nothing is ever sent to it — no mailing list, no confirmation, no newsletter. The privacy page says exactly what is kept and for how long.
x-ratelimit-remaining on any response. If you are getting
close to zero, that is the moment a key is worth the one request.
| Tier | Requests/day | Max page | Sandboxes |
|---|---|---|---|
| Anonymous | 1,000 | 100 | — |
| Free key | 10,000 | 250 | 1 |
That is all of it: flaky is free, and there is no paid tier.
Limits reset at 00:00 UTC. Every response carries x-ratelimit-remaining.
Machine-readable description at /v1/meta, and an
OpenAPI 3.1 spec you can import straight into
Postman, Insomnia or a code generator.