📡 We check pokeapi.co (and 75+ other public dev APIs) with a plain GET every 30 minutes — see the live status page. As of publishing it's up and fast. This is one of the best-run free APIs in existence; nothing below is a reliability complaint.
PokéAPI is probably the most-used tutorial API on the planet: 1,351 Pokémon with sprites, moves, types, evolutions — all of it keyless, CORS-open, community-run, and served fast for a decade. If a coding course asks you to "fetch and render something," odds are it's this. Genuinely great — and if you're browsing the canon read-only, use it happily.
But the moment your tutorial Pokédex becomes an app, four walls appear (all checked live on September 18, 2026):
GET /api/v2/pokemon/pikachu returned 290,912 bytes — every move, every game version, every sprite variant — to render a card that needs a name, an image, and two types. There's no ?fields= to trim it (their GraphQL beta fixes this — credit below)./api/v2/pokemon?limit=20 answers [{"name":"bulbasaur","url":…}, …]. To show a 20-card grid with images and types you make 21 requests (and pull ~5 MB). Every Pokédex tutorial has that Promise.all loop for exactly this reason.?name=pika on the list is silently ignored — full list back, bulbasaur first, no error. Lookup is exact-name-or-id only; a typo (/pokemon/pikachuu) is a 404. Search-as-you-type means downloading all 1,351 names and filtering client-side.POST answers 404. Favourites, teams, a "rate this Pokémon" form — nowhere to go.The fix that keeps your rendering code: extract a compact Pokédex once — using their own GraphQL beta, so it's a single request, no N+1 — and host it on a free mock API you control, with substring search, field selection, sorting, real writes, and GraphQL. No signup, no key.
# 1. one request to PokéAPI's GraphQL beta → 30 compact records (id, name,
# types, sprite URL, height, weight, base_experience) wrapped as db.json
curl -s -X POST https://graphql.pokeapi.co/v1beta2 -H 'content-type: application/json' \
-d '{"query":"{ pokemon(limit:30){ id name height weight base_experience pokemontypes{ type{ name } } } }"}' \
| python3 -c "
import sys, json
rows = json.load(sys.stdin)['data']['pokemon']
recs = [{'id': p['id'], 'name': p['name'],
'types': ','.join(t['type']['name'] for t in p['pokemontypes']),
'sprite': f\"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{p['id']}.png\",
'height': p['height'], 'weight': p['weight'],
'base_experience': p['base_experience']} for p in rows]
print(json.dumps({'pokemon': recs}))" > db.json
# 2. import — the response includes your project id + admin key
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=pokedex" \
-H 'content-type: application/json' --data-binary @db.json
That hosted the first 30 Pokémon for us with their real ids preserved (Pikachu is still /pokemon/25), types flattened to "grass,poison", and sprite pointing at PokéAPI's own hosted sprite images (that's how the tutorials use them anyway). Want 100? Change limit:30. Want different fields? Edit the query — you're shaping your own API now.
| PokéAPI | Your Mockbird project |
|---|---|
/api/v2/pokemon?limit=20&offset=20 → names+URLs only | /pokemon?page=2&limit=20 → full records, total in X-Total-Count. The grid is ONE request. |
search: none (?name=pika silently ignored) | ?name_like=chu → [{"id":25,"name":"pikachu"…},{"id":26,"name":"raichu"…}]; a miss is 200 [] |
| sort: none | ?sortBy=base_experience&order=desc |
| 290 KB per Pokémon, no field trimming | ?select=name,sprite,types — a 20-card grid (their default page size too) is 3.4 KB measured; all 30 records still fit in 5 KB |
filter by type: separate /api/v2/type/:name endpoint, then join client-side | ?types_like=poison on the same list |
/api/v2/pokemon/25 | /pokemon/25 — same id, same idea |
GraphQL: graphql.pokeapi.co beta | /graphql — { pokemon(q:"chu"){ name types } pokemonCount } |
writes: POST → 404 | full CRUD, persists (§3) |
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/pokemon?name_like=chu&select=name"
# → [{"id":25,"name":"pikachu"},{"id":26,"name":"raichu"}]
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/pokemon?sortBy=base_experience&order=desc&limit=2&select=name,base_experience"
# → [{"id":6,"name":"charizard","base_experience":240},{"id":9,"name":"blastoise","base_experience":239}]
Prefer PokéAPI's count/results envelope so existing pagination code keeps working? One param — ?mock_envelope= with {"count":"$total","results":"$data"} — reproduces it exactly (set it once as the project default; details in docs → envelope):
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/pokemon?mock_envelope=%7B%22count%22%3A%22%24total%22%2C%22results%22%3A%22%24data%22%7D&name_like=chu&select=name"
# → {"count":2,"results":[{"id":25,"name":"pikachu"},{"id":26,"name":"raichu"}]}
curl -X POST https://mockbird.mockbird.workers.dev/m/PROJECT_ID/pokemon \
-H 'content-type: application/json' \
-d '{"name":"mockbirdmon","types":"flying","height":3,"weight":9,"base_experience":100}'
# → 201 {"id":31} — and GET /pokemon/31 returns it. It persists.
That upgrades the standard read-only Pokédex into a portfolio piece: a team builder that survives refresh, favourites, ratings, an add-your-own-Pokémon form, a delete button that actually deletes. PATCH and PUT work too.
Poké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/pokemon?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/pokemon?mock_delay=2000"
More recipes in testing loading & error states.
Almost everywhere, honestly — the depth is the product. Moves, abilities, evolution chains, encounter locations, game versions, forms, 1,351 Pokémon of cross-linked canon that no snapshot reproduces; hosted sprite and artwork files; a decade of reliability; and a GraphQL beta (graphql.pokeapi.co) that genuinely solves the over-fetch and N+1 problems server-side — we used it for the extraction above precisely because it's good. Your import is a snapshot, not a live mirror, and it only carries the fields you chose. The honest split: exploring the full canon read-only → pokeapi.co, happily (mind their fair-use policy: cache what you fetch). A compact Pokédex with one-request grids, search, sort, field selection, writes that persist, 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: GET /api/v2/pokemon/pikachu measured 290,912 bytes; /api/v2/pokemon?limit=2 returns name+url pairs only with count: 1351; ?name=pika and ordering= on the list are silently ignored (full list returned, no error); /api/v2/pokemon/pikachuu answers 404 {"status":404,"message":"Not Found"}; POST /api/v2/pokemon answers 404; the GraphQL beta at graphql.pokeapi.co/v1beta2 answered our extraction query correctly. Every Mockbird command on this page was run against a live project before publishing (the 3.4 KB figure is the real measured response for the default 20-record page with ?select=name,sprite,types — 3,352 bytes), and the scratch project was deleted after.
Full API reference in the docs. More guides: Rick and Morty API alternative · Jikan (MyAnimeList) API alternative · SWAPI alternative · JSONPlaceholder alternative · Free GraphQL mock API · Mock API for React. Create your API →