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

Rendered live from the atlas entry
AuthenticationNone required
HTTPSSupported
CORSEnabled
PricingFree
FormatsJSON
Rate limit

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.

Key endpoints
  • GET/cards/named
  • GET/cards/search
  • GET/cards/{id}
trading-card-gamecard-datapricing-datakeyless

Sources

Facts checked Nov 2026