Sitelet https://flakyapi.dev/docs/jsonplaceholder
flaky.

Moving from JSONPlaceholder

Same resources, same field names, same nested routes. The migration is one string in your code. What you get for it is the thing JSONPlaceholder cannot do: ask for the failure you are trying to test against.

The change

- fetch('https://jsonplaceholder.typicode.com/posts')
+ fetch('https://flakyapi.dev/v1/posts')

Two differences in that line: the host, and a /v1 prefix. The prefix exists so the API can change later without breaking the URLs you have already published — the exact problem JSONPlaceholder has, having never versioned.

A find-and-replace across a project is usually enough:

sed -i '' 's|jsonplaceholder.typicode.com/|flakyapi.dev/v1/|g' src/**/*.js

What is identical

Every shared resource returns the same field names, so nothing downstream of the fetch has to change — not your types, not your components, not your tests.

ResourceFieldsJSONPlaceholderflaky
postsid, userId, title, body100100
commentsid, postId, name, email, body500500
albumsid, userId, title100100
photosid, albumId, title, url, thumbnailUrl5,0001,000
todosid, userId, title, completed200200
usersid, name, username, email, address, phone, website, company1010
productsid, title, price, rating, stock, brand, category…—100

Nested routes work the same way — /v1/posts/1/comments, /v1/albums/1/photos, /v1/users/1/todos — and so does filtering by any field, /v1/todos?userId=3&completed=true.

Paging accepts both styles. _start and _limit, the json-server form JSONPlaceholder uses, work unchanged; _page and _limit also work if you prefer pages. So do _sort and _order.

Coming from DummyJSON instead? ?select=title,price works here too, spelled either way — _select is the house style and bare select is accepted so a pasted URL does not silently return an empty array.

What is different, honestly

photos has 1,000 rows, not 5,000

The dataset ships inside the Worker so a list request is an array slice rather than a database read. Five thousand photo rows would be two thirds of the bundle for a resource almost nobody paginates past the first page of. If you genuinely need 5,000, say so and it can go up.

There are rate limits

1,000 requests a day anonymously — enough for development, and no signup. A free key raises it to 10,000 and takes one request with no confirmation email. JSONPlaceholder publishes no limit at all, which sounds better until you need to know whether you are near one: every response here carries x-ratelimit-remaining.

Writes are still fake — but they say so

POST, PUT, PATCH and DELETE are echoed and not stored, exactly as on JSONPlaceholder. The difference is the response carries x-mock-write: not-persisted, so nobody loses an afternoon discovering it. When you need writes that stick, take a sandbox.

An unknown _param is an error

JSONPlaceholder treats ?_typo=1 as a filter on a field named _typo, matches nothing, and returns []. Here it returns a 400 naming the parameters that exist. An empty array that means "you made a typo" costs more time than an error.

The reason to switch

Every fake API hands you data. A couple will slow a response down — DummyJSON takes a ?delay= — but none of them let you ask for the part that actually finds bugs: a specific failure, and an intermittent one. That is normally a local mock server, a branch in your code, or a setTimeout you have to remember to delete.

# your loading state, with a real slow response
GET /v1/posts?_delay=3000

# your error boundary, with a real 503
GET /v1/posts?_status=503

# your retry logic, failing one request in three
GET /v1/posts?_fail_rate=0.3

# slow and unreliable at once, because that is what production is
GET /v1/products?_delay=1500&_fail_rate=0.2

It works on every endpoint, including nested routes. No install, no config, no branch in your code that has to be removed before you ship — and it works from a browser console, a CodePen, or a colleague's machine with nothing set up.

Out-of-range values are rejected rather than ignored: ?_status=999 returns a 400, not a silent 200. A tool for testing failures should not fail quietly itself.

Try it before you change anything

Paste either of these into a terminal — nothing to sign up for.

curl 'https://flakyapi.dev/v1/posts?_limit=2'

curl -i 'https://flakyapi.dev/v1/posts?_status=503'

Or use the live inspector on the home page, which lets you set the delay, status and failure rate and watch the response change.

There is also an OpenAPI 3.1 spec — import it into Postman or Insomnia and every endpoint, including the chaos parameters, arrives already described. JSONPlaceholder does not publish one.