data.placeholder.com/api
Mock REST API
A free fake REST API for prototypes, tutorials, tests and AI agents, in the style of JSONPlaceholder and json-server. Eight related resources, filters, sorting, paging and simulated writes. Same URL, same bytes, forever.
const posts = await fetch("https://data.placeholder.com/api/posts?userId=1").then(r => r.json());
Resources
| Resource | Count | Belongs to | Nested routes |
|---|---|---|---|
/api/users | 10 | /api/users/1/posts, /albums, /todos, /orders | |
/api/posts | 100 | userId | /api/posts/1/comments |
/api/comments | 500 | postId | |
/api/albums | 100 | userId | /api/albums/1/photos |
/api/photos | 5000 | albumId | |
/api/todos | 200 | userId | |
/api/products | 50 | ||
/api/orders | 100 | userId |
Children are blocked per parent like JSONPlaceholder: user 1 owns posts 1 to 10, post 1 owns comments 1 to 5, album 1 owns photos 1 to 50. /api returns a machine-readable index of all of this.
Users are synthetic personas: user N is persona <country>-N, with a realistic name, address, example.com e-mail, fictional-range phone and a matching avatar. By default the ten users cycle through US, GB, SE and NO; ?country=SE makes them all Swedish (SE, NO, GB, US available).
Examples
https://data.placeholder.com/api/usersall 10 usershttps://data.placeholder.com/api/users?country=SEthe same 10 users, all Swedishhttps://data.placeholder.com/api/users/1/postsposts by user 1https://data.placeholder.com/api/posts?userId=1&_sort=titlefilter and sorthttps://data.placeholder.com/api/posts?_page=2&_limit=5page 2, 5 per page, with aLinkheaderhttps://data.placeholder.com/api/posts/1?_embed=comments&_expand=usera post with its comments and authorhttps://data.placeholder.com/api/comments?q=dolorfull-text searchhttps://data.placeholder.com/api/todos?completed=false&userId=3open todos for user 3https://data.placeholder.com/api/albums/1/photos?_limit=3first three photos of album 1https://data.placeholder.com/api/products?category=home&price_lte=100home products up to $100https://data.placeholder.com/api/orders?status=delivered&_sort=-totaldelivered orders, largest firsthttps://data.placeholder.com/api/users?address.city_like=newdot paths into nested fields
Query parameters
| Parameter | Meaning |
|---|---|
<field>=<value> | Exact match. Dot paths reach nested fields (address.city). Repeat a key for OR (?id=1&id=2). |
<field>_ne, _like, _gte, _lte, _gt, _lt | Not equal, case-insensitive substring, and comparisons (numbers or strings). |
q | Case-insensitive full-text search over all string values. |
_sort, _order | _sort=title&_order=desc or _sort=-title. Comma lists sort by several fields. |
_page, _limit | Pagination. _limit defaults to 10 when _page is set. Adds an RFC 8288 Link header (first, prev, next, last). |
_start, _end, _limit | Slicing, json-server style. |
_embed | Include children: /api/posts?_embed=comments, /api/users/1?_embed=posts,todos. |
_expand | Include the parent: /api/posts?_expand=user, /api/comments/1?_expand=post. |
country | Users from one persona country; also applies to _expand=user. |
List responses carry X-Total-Count (matches before paging). Both it and Link are exposed to browser JavaScript via CORS. Bad parameters return 400 with a JSON message saying what is allowed.
Writes
Writes behave like a real API but nothing is stored, and the next GET returns the original data:
| Request | Response |
|---|---|
POST /api/<resource> | 201, your body with a new id (count + 1) and a Location header. A nested POST /api/posts/1/comments also sets postId. |
PUT / PATCH /api/<resource>/<id> | 200, the stored object shallow-merged with your body |
DELETE /api/<resource>/<id> | 200, {} |
curl -X POST https://data.placeholder.com/api/posts \
-H "Content-Type: application/json" \
-d '{"title": "Hello", "userId": 1}'
# 201 Created, Location: /api/posts/101
# {"title": "Hello", "userId": 1, "id": 101}
Bodies must be JSON objects, up to 1 MB. Write responses are Cache-Control: no-store.
Images in the data
photos.url, photos.thumbnailUrl, products.image and products.thumbnail point at classic via.placeholder.com images, and users.avatar at an avatar. All of them are caught by placeholder-check if they end up in a build.
Determinism
All GET responses are frozen (v1) and cached for a year. Every value is derived from a hash of a fixed key, never from the clock or a random number generator, so tests can assert on exact values.