Issue Nº 55 — Nov 4, 2026
Scryfall returns a 404 for zero matches and caches it for two days
Issue fifty-five covers Scryfall in the Games & Comics category. It is a Magic: The Gathering card database that needs no key and sends a wildcard CORS header. We queried it live on 4 November 2026. A search with no matches is an error, not an empty list. A bad search term can be dropped with only a warning. Read the status code and the warnings field.
Zero matches is a 404
We sent GET https://api.scryfall.com/cards/search?q=xyzzyqq. The status was 404 and the body had code not_found. The details text said: Your query didn’t match any cards.
A search that matches nothing is an error, so a client that expects an empty data list will break. Handle the 404 as a normal result.
Paging has its own error. GET /cards/search?q=t:goblin&page=999 returned 422 with code validation_error. The details said we had paginated beyond the end of the results.
Bad terms: a 400 or a quiet warning
GET /cards/search?q=foo:bar returned 400 with code bad_request. The warnings array said: Invalid expression “foo:bar” was ignored. Unknown keyword “foo”. The details said all of our terms were ignored.
Now add one good term. GET /cards/search?q=t:goblin+foo:bar returned 200 with total_cards 561. The same warning was in the body. The API dropped the bad term and ran the rest.
So a 200 does not mean your query ran as you wrote it. Read the warnings array on every search response and show it to the user.
Fuzzy names: the docs and the live API differ
The documentation for /cards/named says that fuzzy=jac bele matches Jace Beleren. Live, GET /cards/named?fuzzy=jac+bele returned 404 with type ambiguous. The details said too many cards match, and asked for more words. fuzzy=bolt gave the same result.
A fuzzy lookup can also return a token. GET /cards/named?fuzzy=dragon returned 200. The card was named Dragon, with layout token and set tund (Unsanctioned Tokens). It was not a playable card.
Check the layout field before you treat a fuzzy hit as a real card. Use exact when you know the full name.
Caching, CORS and pages
We sent Origin: https://example.com with GET /cards/named?exact=Notacard. The 404 had access-control-allow-origin: * and cache-control: public, max-age=172800. That is two days. A miss is cached for as long as a hit, so a card you add or fix may not show for two days.
A search page holds up to 175 cards. GET /cards/search?q=t:goblin returned 175 cards in 918,746 bytes, with total_cards 561 and has_more true. The next_page value is a full URL with every default filled in, such as order=name and unique=cards. Follow it as it is.
A card response from /cards/named sends x-scryfall-card, the scryfall.com page URL, and x-scryfall-card-image, a cards.scryfall.io image URL.
Manifest and rate limits
GET /cards/manifest?lang=en&page=1 returned 200 with 15,000 entries and total_cards 117364. The body was 3,941,656 bytes. Each entry has only id, oracle_id, name, set_code, collector_number, lang and three timestamp fields. Use it to find out which cards changed, not to read card data.
The rate-limits page lists hard limits. /cards/search, /cards/named, /cards/random and /cards/collection allow 2 requests a second. /cards/manifest allows 10 a minute. Other methods allow 10 a second. A 429 response limits your access for 30 seconds. We did not trigger one. We cite the documentation.
Scryfall, by the numbers
10 requests/second for most methods; 2/second on /cards/search, /cards/named, /cards/random and /cards/collection; 10/minute on /cards/manifest. Per scryfall.com/docs/api/rate-limits. A 429 locks access for 30 seconds.
- GET/cards/named
- GET/cards/search
- GET/cards/{id}