๐ก We check rickandmortyapi.com (and 75+ other public dev APIs) with a plain GET every 30 minutes โ see the live status page. As of publishing it's up, fast, and answering with 90-day immutable cache headers. This is one of the best-run free APIs there is.
The Rick and Morty API is the fetch-and-render staple of modern React and GraphQL tutorials: 826 characters with hosted portrait images, episodes and locations cross-linked, REST and GraphQL, keyless, CORS open, no rate limit, aggressive caching. Community-run and genuinely excellent โ if you're building a read-only character grid, use it and enjoy it.
But three walls show up the moment the tutorial becomes an app (all checked live on September 18, 2026):
/api/character/?name=ricl (one typo) answers 404 {"error":"There is nothing here"}. fetch doesn't reject on 404, so res.json() happily parses the error object, data.results is undefined, and data.results.map(โฆ) throws TypeError. Every search-as-you-type demo built on this API crashes on the first typo unless you special-case it. Same for episodes (/api/episode/?name=zzz โ 404) and for paging past the end (?page=999 โ 404).POST, PUT, and DELETE on any route all answer 404 {"error":"There is nothing here."}. Favourites, ratings, an "add your own character" form โ nowhere to go.The fix that keeps your rendering code: copy real records into a free hosted mock API you control โ same field names (name, status, species, image, even the nested origin/location objects and the episode URL array, verbatim), plus a search that answers [] on a miss, the exact info/results envelope if you want it, writes that persist, and GraphQL. No signup, no key.
# 1. pull live Ricks from the Rick and Morty API, wrap as db.json
curl "https://rickandmortyapi.com/api/character/?name=rick&status=alive" \
| python3 -c "import sys,json; print(json.dumps({'characters': json.load(sys.stdin)['results'][:10]}))" \
> db.json
# 2. import โ the response includes your project id + admin key
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=rickmorty" \
-H 'content-type: application/json' --data-binary @db.json
That hosted 10 characters for us verbatim: numeric ids preserved, origin still the nested {"name":"Earth (C-137)","url":โฆ} object, Rick's episode array all 51 URLs long, image pointing at their hosted portrait (hotlinking their avatars for a dev project is how every tutorial uses them anyway). Your card component renders it unchanged.
| Rick and Morty API | Your Mockbird project |
|---|---|
/api/character/?page=2 | /characters?page=2&limit=20 โ total in the X-Total-Count header instead of info.count (or see ยง3 for the exact envelope) |
?name=rick (case-insensitive substring) | ?name_like=rick โ same semantics; a miss is 200 [], never a 404 |
?status=alive&species=Human | ?status=Alive&species=Human โ exact match, so mind the capitalisation as stored |
/api/character/1 | /characters/1 โ a bad id is a real 404 in both, but list searches never are |
?page=999 past the end โ 404 | 200 [] โ your pagination code needs no special case |
| search fields limited to name/status/species/type/gender | any field: ?gender=Female, ?species_ne=Human, ?q=sanchez (any-field substring), ?select=name,image |
graphql โ { characters { results { name } } } | /graphql โ { characters(q:"rick") { name status } charactersCount } |
POST/PUT/DELETE โ 404 | full CRUD, persists (ยง4) |
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters?name_like=sanchez&select=name"
# โ [{"id":1,"name":"Rick Sanchez"}]
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters?name_like=zzznope"
# โ [] โ 200 + empty ARRAY. The typo case renders "no results", not a crash.
info/results envelope? One paramTutorial code reads data.results and data.info.count. Reproduce that shape:
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters?mock_envelope=%7B%22info%22%3A%7B%22count%22%3A%22%24total%22%7D%2C%22results%22%3A%22%24data%22%7D&name_like=sanchez&select=name"
# โ {"info":{"count":1},"results":[{"id":1,"name":"Rick Sanchez"}]}
# and on a miss:
# โ {"info":{"count":0},"results":[]} โ results is still an ARRAY. No 404, no undefined.
Set it once as the project default (PUT /api/projects/:id/settings {"envelope":โฆ}) and every GET answers in their shape โ your fetch layer doesn't change at all. Details in docs โ envelope.
curl -X POST https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters \
-H 'content-type: application/json' \
-d '{"name":"Mockbird Rick","status":"Alive","species":"Bird-Person","gender":"unknown"}'
# โ 201 {"id":โฆ} โ and GET /characters/:id returns it. It persists.
That upgrades the standard read-only character grid into a portfolio piece: favourites that survive refresh, an add-a-character form, an edit page, a delete button that actually deletes.
The Rick and Morty API is so reliable you can't practise failure against it. Yours can fail on demand:
# two 429s, then success โ exercise retry/backoff
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters?mock_seq=429,429,200&mock_seq_key=drill1"
# 2-second response for your skeleton state
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/characters?mock_delay=2000"
More recipes in testing loading & error states.
Credit where due โ a lot. The content is the product: the real show's characters, episodes, and locations, cross-linked, with hosted portrait images, served fast behind 90-day immutable caching, keyless and unlimited, maintained for years. Its GraphQL resolves relations server-side (character โ episodes โ characters), while our copy keeps origin/location/episode as the verbatim nested values โ data to render, not links to traverse. And your import is a snapshot, not a live mirror. The honest split: read-only fetch demos and GraphQL joins over the show's canon โ rickandmortyapi.com, happily. Your own records with miss-proof search, writes that persist, the same envelope, and failure drills โ your import.
The two-curl block in section 1, or zero setup at all:
curl https://mockbird.mockbird.workers.dev/m/demo/products?limit=3
Facts checked live on September 18, 2026: /api/character/?name=zzzznotachar, /api/episode/?name=zzzz, and /api/character/?page=999 all answer HTTP 404 {"error":"There is nothing here"}; POST /api/character, PUT /api/character/1, and DELETE /api/character/1 all answer 404 {"error":"There is nothing here."}; /api/character/99999 answers 404 {"error":"Character not found"}; /api/character/ reports info.count 826 across 42 pages; responses carry cache-control: public, max-age=7776000, immutable; ?name=RICK matched 107 characters (case-insensitive substring). Every Mockbird command on this page was run against a live project before publishing, and the scratch project was deleted after.
Full API reference in the docs. More guides: Jikan (MyAnimeList) API alternative ยท xkcd API CORS fix ยท SWAPI alternative ยท JSONPlaceholder alternative ยท Free GraphQL mock API ยท Mock API for React ยท Host a JSON file as an API. Create your API โ