GuidesTEST APIS

Free fake APIs for testing a React app

We tested 13 free fake APIs from a browser page on 26 October 2026: 11 send CORS headers, so you call them with fetch() directly and no proxy, and 9 of those accept POST. JSONPlaceholder, DummyJSON and ReqRes answer 201 to a write and store nothing, while Platzi Fake Store API and restful-api.dev really save it. Postman Echo was the only one with no CORS headers.

Results by API

Every test ran from a page on http://127.0.0.1 in Chromium 156 and Firefox 157, with headers checked again in curl. Random User and PokeAPI are read only: Random User returns up to 5,000 fake users per request with ?results= (add seed= to get the same users every time), and PokeAPI is GET only with no key and no rate limit since November 2018, though its fair use policy asks you to cache. For real data instead of fake data, see the 70 free public APIs that work from the browser.

Free test APIs called from a browser page, checked October 2026
APIDataWrites persistKey or headersCORS
JSONPlaceholder100 posts, 500 comments, 100 albums, 5,000 photos, 200 todos, 10 usersNo, fakedNoneYes, all methods
DummyJSON194 products, 208 users, 208 carts, 1,454 quotes, 50 recipes and moreNo, fakedNone, JWT login with a demo userYes, all methods and Authorization
Fake Store API20 products, 7 carts, 10 usersNo, fakedNone, token loginYes, but headless Chromium got a 403 bot check
Platzi Fake Store APIProducts, categories and users (22, 10 and 34 when tested)Yes, shared with everyoneNone, JWT loginYes, all methods
ReqRes12 users, login and registerNo, nothing is storedNone for the public demoYes, all methods
httpbinEchoes your request, any status code, delays up to 10 secondsNothing to storeNoneYes, all methods
Postman EchoEchoes your request, status codes, delaysNothing to storeNoneNo header at all
restful-api.dev13 sample objects plus the ones you createYes, 50 requests per dayNone, x-api-key for 100 per dayYes, all methods
Random UserUp to 5,000 fake users per requestRead onlyNoneYes
PokeAPI1,351 Pokemon and 1,025 speciesRead only, GET onlyNoneYes
MockAPI.ioResources you define, 1 project with 2 resources freeYes, in your projectFree accountYes, no PATCH
BeeceptorYour own mock endpoint, 50 requests per day freeUp to 10 objects on CRUD routesNoneYes, all methods

Two of the 13 did not work. Postman Echo (postman-echo.com) has the same routes as httpbin, but it sends no Access-Control-Allow-Origin header on any response, not even on OPTIONS. My JSON Server (my-json-server.typicode.com) answered HTTP 530 with Cloudflare error 1016 from two networks on the test day, home page included.

Fake writes or real writes

A React create form usually sends a POST, then refetches the list. With JSONPlaceholder the form gets a 201 and the new item never shows up. That is by design: its own guide says the resource is not really created and the response is faked.

Write, then read it back:

  • JSONPlaceholder: POST /posts returned 201 with id 101, then GET /posts/101 returned 404. PUT and DELETE return success and change nothing. The preflight allows every method and the Authorization header.
  • DummyJSON: POST /products/add returned 201 with id 195, then GET /products/195 was 404.
  • Fake Store API: POST /products returned 201 with id 21, then GET /products/21 came back with an empty body.
  • ReqRes: POST /api/users returned 201 with an id that 404s on read. Its home page says "Anonymous demo, rate-limited per IP. No data is stored."
  • Platzi Fake Store API (api.escuelajs.co): a POST to /categories created id 94, a GET returned it and a DELETE removed it. Everyone shares the same data, so other people's test entries named "string" and "New Ctg" sat in the list and the counts in the table will drift.
  • restful-api.dev: POST /objects created an object, GET returned it, DELETE removed it. Ids 1 to 13 are sample objects you cannot change; a PUT on id 7 returns 405.

MockAPI.io and Beeceptor are built to store data too, but both need setup first, so we did not write to them. MockAPI's free plan is 1 project with 2 resources; $5 a month or $35 a year gets 20 projects and 50 resources. Its project URLs allow GET, PUT, POST and DELETE cross-origin, but PATCH is missing from the allowed methods. Beeceptor's free plan needs no signup and gives 50 requests per day per endpoint, 3 mock rules and CRUD routes that keep 10 small objects; unclaimed free endpoints are deleted after 30 days.

Faked writes are fine for testing loading and success states. When the UI must show the new item after a reload, use Platzi or restful-api.dev, or keep the created item in local state.

JSONPlaceholder: read posts and send a POST from the browser
const api = 'https://jsonplaceholder.typicode.com';

const posts = await fetch(`${api}/posts?userId=1`).then((response) => response.json());

const response = await fetch(`${api}/posts`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Hello', body: 'Sent from the browser', userId: 1 }),
});
const created = await response.json();

// Writes are faked: the new id answers 404 when you read it back
const readBack = await fetch(`${api}/posts/${created.id}`);

console.table(posts.slice(0, 3).map(({ id, title }) => ({ id, title })));
console.log(response.status, created, readBack.status);

Login, tokens and pagination

DummyJSON covers login and paging on one host. POST /auth/login with the demo user emilys and password emilyspass returns a JWT accessToken. Send it as Authorization: Bearer TOKEN to GET /auth/me and you get the user back. A bad token returns 401 with {"message":"Invalid/Expired Token!"}, and the browser can read that body. The JSON body and the Authorization header both trigger a CORS preflight, which DummyJSON answers.

For paging, use limit and skip, and select to trim fields: /products?limit=10&skip=20&select=title,price returns products 21 to 30. limit=0 returns all 194 products in one call. Fake Store API (/auth/login) and Platzi also have token login.

DummyJSON: log in, send the token and page through products
const api = 'https://dummyjson.com';

const login = await fetch(`${api}/auth/login`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: 'emilys', password: 'emilyspass', expiresInMins: 30 }),
});
if (!login.ok) throw new Error(`Login failed: ${login.status}`);
const { accessToken } = await login.json();

const me = await fetch(`${api}/auth/me`, {
  headers: { Authorization: `Bearer ${accessToken}` },
}).then((response) => response.json());

const page = 3;
const pageSize = 10;
const { products, total } = await fetch(
  `${api}/products?limit=${pageSize}&skip=${(page - 1) * pageSize}&select=title,price`,
).then((response) => response.json());

console.log(me.username, `page ${page} of ${Math.ceil(total / pageSize)}`);
console.table(products);

Error and loading states

httpbin returns any status you ask for: /status/500 gives a 500, and 503 and 429 behaved the same. It reflects your Origin in the CORS header, so fetch() can read the status instead of failing with a network error, which is what you need to test an error banner or a retry button.

For spinners and skeleton screens, slow the response down on purpose. httpbin's /delay/3 waits 3 seconds and caps at 10 (/delay/12 answered after 10.4 seconds). DummyJSON takes ?delay= in milliseconds on any route: delay=3000 answered in 3.3 seconds, and the maximum is 5000 (6000 returns a 400). Pair the slow calls with AbortSignal.timeout() so your component shows a timeout state instead of spinning forever.

httpbin and DummyJSON: trigger errors, slow responses and timeouts
async function probe(url, timeoutMs = 5000) {
  const started = performance.now();
  try {
    const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
    return { url, status: response.status, ok: response.ok, ms: Math.round(performance.now() - started) };
  } catch (error) {
    return { url, status: error.name, ok: false, ms: Math.round(performance.now() - started) };
  }
}

const results = await Promise.all([
  probe('https://httpbin.org/status/500'),
  probe('https://httpbin.org/status/429'),
  probe('https://httpbin.org/delay/3'),
  probe('https://httpbin.org/delay/8'),
  probe('https://dummyjson.com/products/1?delay=2000&select=title'),
]);

console.table(results);

When a test API blocks the browser

Three things blocked our browser tests:

  • Postman Echo sends no CORS headers. Chromium throws TypeError: Failed to fetch (see Failed to fetch) and Firefox throws TypeError: NetworkError when attempting to fetch resource. A GET through https://proxy.cors.dev/https://postman-echo.com/get worked in both, since the free cors.dev proxy relays GET and HEAD text responses up to 1 MiB with no key. To proxy a POST or custom headers you need cors.dev Pro at $5 a month, but httpbin and Beeceptor accept POST directly, so use them instead.
  • Fake Store API has a Cloudflare bot check. It sends Access-Control-Allow-Origin: * and works in Firefox, but headless Chromium, the browser Playwright and Puppeteer use in CI, got a 403 challenge page and fetch() failed. Run end-to-end tests against DummyJSON instead.
  • restful-api.dev limits are per IP. The public API allows 50 requests per day (100 with a free x-api-key), reset every 24 hours. Through a shared proxy IP it had already answered 405 "daily request limit", so call it directly and each visitor gets their own 50.

Good questions.

Which free fake API is best for a React app?

DummyJSON, if you need login, paging and artificial delays on one host; JSONPlaceholder for simple lists like /posts and /users. If created items must show up after a refetch, use restful-api.dev or Platzi Fake Store API.

Does JSONPlaceholder save POST requests?

No. POST /posts returns 201 with id 101, but GET /posts/101 returns 404, and PUT and DELETE change nothing. The responses are faked by design.

Which free test APIs actually store data?

Platzi Fake Store API and restful-api.dev stored our writes and returned them on the next GET. Platzi shares one database with everyone; restful-api.dev allows 50 requests per day per IP. MockAPI.io and Beeceptor store data once you set up a project.

Do I need an API key for ReqRes?

No. The anonymous demo worked with no key and no signup and reported a limit of 40 requests per day, reset at midnight UTC. Its preflight still allows an x-api-key header, so old tutorials that send one keep working.

How do I test a 500 error or a slow response?

Call https://httpbin.org/status/500 for an error status the browser can read and https://httpbin.org/delay/3 for a slow response (10 seconds maximum), or add ?delay=3000 to a DummyJSON route (5000 ms maximum).

Why does a test API work in Postman but not in my React app?

Postman is not a browser, so it never checks CORS headers; Postman vs the browser covers the details. Postman Echo is the clear case: fine in Postman, blocked in every browser because it sends no Access-Control-Allow-Origin header.