Test Data — entry 009 of 19
JSONPlaceholder
JSONPlaceholder is one of the longest-running free fake REST APIs, serving a fixed set of 100 posts, 500 comments, 10 users, 100 albums, 5,000 photos, and 200 todos as JSON. Every standard verb works against each resource, including nested routes like /posts/1/comments, though POST/PUT/PATCH/DELETE only simulate a write and return a fake response without persisting any change. Its data and IDs never change, which is exactly why it's the default backend in countless framework tutorials and testing guides.
JSONPlaceholder is a free, keyless fake REST API serving a fixed, never-changing dataset -- 100 posts, 500 comments, 10 users, 100 albums, 5,000 photos, 200 todos -- with every standard verb wired up against each resource, including nested routes like /posts/1/comments. A live GET this run confirmed both http:// and https:// resolve the same host directly with no redirect either way, and every response (200 or 404) carries an undocumented x-ratelimit-limit: 1000 / x-ratelimit-remaining / x-ratelimit-reset header trio that appears nowhere in the project's own docs.
GreatAPIs Score
Auth quickstart
- No API key, header, or signup required for any route. This run's undocumented x-ratelimit-limit header consistently reads 1000, but the paired x-ratelimit-reset value is not stable across probes -- the window it implies varies from one request to the next and between edge servers, so it can't be read as a dependable 'N requests per minute' figure. Treat the counter as headroom to watch, not a number to plan around.
Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.
Get a single post
GEThttps://jsonplaceholder.typicode.com/posts/1
{"userId":1,"id":1,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}Try it
Developer reference
https://jsonplaceholder.typicode.com- GET/posts/{id}
- GET/posts/{id}/comments
- GET/users
Gotchas & limits
- A not-found ID is HTTP 404 with a literal `{}` body (`content-length: 2`), not an empty array or a 200 with `null`: a live `GET /posts/999` this run returned exactly that -- branch on status code, never on an empty-looking body, since there's no array shape here to check.
- A non-numeric ID also 404s the same way rather than erroring: a live `GET /posts/abc` this run returned the identical `{}` at 404 -- the API never validates the ID's type, it just fails the lookup.
- CORS echoes the request's `Origin` back verbatim instead of sending a wildcard: a live GET with `Origin: https://greatapis.com` this run returned `access-control-allow-origin: https://greatapis.com` on that exact value, while the same GET sent with no `Origin` header comes back with no `access-control-allow-origin` at all -- though `access-control-allow-credentials: true` and `vary: Origin` are still present either way, so it's that one header that's conditional, not CORS support in general. Verified on cache-busted requests (`cf-cache-status: MISS`) so the edge cache isn't masking the difference.
- `POST /posts` returns HTTP 201 with a fake `id: 101` that is never actually persisted: a live POST this run got back `{"title":"foo","body":"bar","userId":1,"id":101}`, but an immediate follow-up `GET /posts/101` still 404s with the same literal `{}` -- treat every write as a simulation, not real state.
- An `OPTIONS` preflight against `/posts/1` (sent with `Origin` + `Access-Control-Request-Method: GET`) returns 204 with `access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE` and the same echoed-origin header, confirming a real browser cross-origin call would succeed end to end.