Fanatical MCP — response reference

This page documents the response shape of every Fanatical MCP tool. Read it alongside /for-ai. The tool outputSchema already carries this information in machine-readable form; this page adds context and gotchas that a schema alone does not carry.

Response wrapper

Every tool response is an object with region and currency at the top level:

{
  "region": "GB",
  "currency": "GBP",
  "results": [ ... ]     // search_products
  "product": { ... }     // get_product (or null)
  "products": [ ... ],   // get_products
  "not_found": [ ... ],  // get_products — the identifiers that matched no product
  "lookup_by": "slug"    // get_products — "slug" or "steam_id"
  "sales":   [ ... ]     // list_sales
  "values":  [ ... ],    // list_facets
  "facet":   "tags"      // list_facets — the facet field these values belong to
}

Every monetary field inside the response uses the top-level currency. Agents do not need to check currency on individual records.

get_product returns { region, currency, product: null } when the product is not found or not available in the region. The null result is not an error; it is a valid answer.

get_products reports a miss differently. The products array holds only the products that were found. It never holds null entries. Each identifier that matched no product goes into not_found. The lookup_by field tells you what not_found holds: slug strings, or Steam application IDs.

Products come back in the order you asked for them, and each record carries the identifier it answers. Match a record to your request by its slug or steam_id field. Do not match by array position, because the misses are absent from the array.

get_product and get_products are direct lookups: they return preorder and out-of-stock products with no filtering. search_products excludes both by default. This asymmetry is intentional on both sides — a point lookup answers "what is this product", a search answers "what can a customer buy right now".

Product record

Returned by search_products (as items of results), by get_product (as product), and by get_products (as items of products). get_product and get_products return the same full record. search_products returns a trimmed subset.

Identity

product_id
Internal identifier. Regional and edition variants have distinct product_id values. Stable while a listing is live. A re-listed product may get a new product_id.
slug
URL slug. Shared across regional variants of the same product. Use this with get_product.
steam_id
Steam identifier, usually the application identifier, or null when the product is not on Steam. Not unique in the catalogue: a DLC or add-on kit carries the identifier of its base game. A lookup by steam_id returns the base edition, which is the record with a null parent. To get a specific edition, look it up by slug.
name
Display name for the user. Plain text — no markup, no HTML entities.
type
Fine-grained product type. 18 possible values including game, dlc, bundle, game-bundle, mystery-bundle, book, software, gift-card. Note: search_products.type accepts only three input values: game, dlc, or bundle. The bundle input matches the output values bundle, game-bundle and mystery-bundle. A non-null bundle_price_messages marks a multi-item pack regardless of type — check that field, not type, to detect packs.

Media and description

Rendering product cards: the image and vertical_image URLs are the canonical Fanatical cover art for the product, served from cdn-ext.fanatical.com, a CDN configured for external hotlinking. Prefer these URLs over running a separate image search on the product name — an image search often picks up store-listing screenshots from competitor retailers, and Fanatical's own art has correct licensing and current promotional overlays.
short_description
Plain-text summary, up to about 240 characters. No markup, no HTML entities. Line breaks are newline characters. Falls back to a truncated full description when no short description is set.
url
Canonical English product URL with tracking parameters. Use exactly as returned — do not strip the query string. The page shows the product. It does not add the product to the basket.
image
Primary cover image URL. 1280x720 landscape. Served from cdn-ext.fanatical.com.
vertical_image
Portrait cover image URL. 600x900. Null when not available.

Action URLs

The record holds two URLs that make the site do something when the page opens. Use them to give the user a direct action in your answer, instead of only a link to a page.

cart_url
Add-to-cart URL. The product page opens with the product already in the basket. If the product has a coupon, the site also applies the coupon code. Use this URL as the target of an “Add to cart” action. It is null for a bundle where the customer must select a tier, or must select which items they want — one URL cannot give a definite basket for those bundles. Link to url instead.
coupon.coupon_url
Apply-coupon URL. The product page opens and the site applies the coupon code. It does not put the product in the basket. Use this URL as the target of an “Apply coupon” action. It is present only when coupon is not null.
Calls to action in a chat reply: give the user the action, not the instruction. Make cart_url the primary action — “Add to cart” — and coupon.coupon_url the second action — “Apply coupon FANATICAL15”. Use cart_url on its own when the product has a coupon, because that URL does both steps. Both URLs carry the same tracking parameters as url. Use them exactly as returned.
No URL completes a purchase. These URLs only prepare the basket or apply the discount. The user must complete the purchase on the site. Do not tell the user that a link buys the product.

Prices and discount

price
Current price after any non-coupon discount, in the region's currency. Null when no price is set for this currency.
full_price
Original price before any discount, in the region's currency. Equal to price when not on sale.
discount_percent
Percent off list price, integer 0 to 100. Zero when not on sale.
Note: "discount" is percent off, not absolute saving. A high-discount cheap item ranks above a low-discount expensive item under sort: "discount". For pick-and-mix bundles, this represents the saving versus RRP of the constituent items.

Sale membership

sale_slug
Slug of the curated sale event this product is in, or null when not in a curated sale.
Independence: a product with sale_slug: null can still be discounted. sale_slug indicates curated sale membership, not the presence of a discount. Use discount_percent > 0 to test for a discount.
sale_name
Human-readable sale event name, or null when sale_slug is null.
promo_label
Highest-priority promotional label, or null when no label applies.
Priority: star-deal > best-ever > highlighted-deal > flash-sale. Only the highest-priority applicable label is shown. This is not a discount ranking — a star-deal is not necessarily a bigger discount than a flash-sale. It reflects promotional weight, not price.

Steam compatibility

steam_deck_support
Valve's Steam Deck compatibility rating: verified, playable, or unsupported. Null when Valve has not rated the product.
steam_machine_support
Same enum, same semantics.

Taxonomy

Every taxonomy field comes as two arrays: display names and slugs. Display names go to users; slugs go back to search_products.

tags, tags_slugs
Category tags: genres, themes, playstyles. Slugs go to search_products.tag.
developers, developers_slugs
Developer studios. Slugs go to search_products.developer.
publishers, publishers_slugs
Publishers. Slugs go to search_products.publisher.
franchises, franchises_slugs
Franchises. Slugs go to search_products.franchise.
Silent zero results: filter slugs must come from list_facets or from a prior product record. A guessed slug that does not exist returns zero results with no error. Always look up the slug first.

Availability and platform

in_stock
Purchasable at the last ingest cycle. May be stale by up to ten minutes.
drm
Primary DRM platform (e.g. steam, drm free, gog, epicgames). Null when no DRM applies.
operating_systems
Supported operating systems. Values: windows, mac, linux, xbox series, xbox one.
vr_support
VR headset and feature flags. Empty array when the product has no VR support.

Reviews

metacritic_score
Metacritic score. Number when available; some source records provide it as a string. Null when no score.
steam_review_score
Steam review score. The scale is 1 to 9. It is not a percentage. 1 is Overwhelmingly Negative, 5 is Mixed, 8 is Very Positive, and 9 is Overwhelmingly Positive. A score of 0 means either that Steam has too few reviews to rate the product, or that this listing has no review data of its own. Usually a string; some source records give a number. Null when the product has no Steam review data. To filter on this field, use search_products.min_steam_review_score.
steam_review_score_desc
Steam review label for steam_review_score, for example Very Positive. If the score is 0, the label gives the number of reviews, for example No user reviews or 3 user reviews. Null when the product has no Steam review data.

Dates

release_date
Release date as Unix seconds. Null when undated. May be in the future for preorders.
valid_until
When the current price and availability expire, as Unix seconds. Null when open-ended.
Not just sale end. valid_until covers any reason the current state may expire: sale end, coupon end, key stock expiry, or a scheduled price change. Compare to the current time to gauge urgency. Use sort: "valid_until_asc" to surface anything ending soonest.
last_modified
Unix seconds of the last change to this product's data. Moves whenever any field on the record changes. Useful for cache invalidation or for detecting what changed since a prior read.

Coupon

The coupon field is null unless an active voucher applies to this product.

coupon.code
Coupon code the user enters at checkout.
coupon.percent
Additional discount percent applied by the coupon, on top of the base discount.
coupon.coupon_price
Final price after applying the coupon, in the region's currency.
coupon.coupon_saving
Amount saved by applying the coupon versus the base price.
coupon.valid_until
Coupon expiry as Unix seconds. Null when the coupon has no expiry.
coupon.name, coupon.description
Human-readable name and description of the coupon.
coupon.coupon_url
Apply-coupon URL. See Action URLs above, in the same product record.

search_products returns a smaller coupon than get_product. A search result gives code, percent, coupon_price and coupon_url. Use get_product when you need name, description, coupon_saving or valid_until.

The price does not include the coupon. price and discount_percent show the product before the coupon. Read coupon_price for the price the user pays with the code. If you quote price for a product that has a coupon, you quote too much.
Two ways to apply the code: coupon.coupon_url applies the code. cart_url adds the product and applies the code in one step. Do not build these URLs yourself — the server returns them with the tracking parameters already in place.

Parent, editions and DLC

Products in the catalogue are related in one direction: a variant, an edition, or a DLC has a parent. The record shows this relationship from both ends.

parent
Null unless this product is a variant, edition, or DLC of a parent. When present, it carries the parent's name, type, slug, url, and image.
editions
The other editions of the same game — the base game, and the deluxe, premium, complete and similar versions. Each entry gives slug, name, type, image, url, price and discount_percent. The url already carries tracking parameters, so use it as it is. Entries carry no drm: assume the same DRM as the product you asked for.
editions_count
The number of editions available in the region.
dlc
The DLC and the add-ons for this game, in the same shape as editions.
dlc_count
The number of DLC available in the region.

The editions and dlc arrays hold a maximum of 10 items each. The two count fields give the true totals. When a count is more than 10, use search_products with the franchise or with a text query to get the remainder.

A product does not appear in its own arrays. The arrays hold the rest of the group:

The dlc array is the DLC for the game group, not for the specific edition you asked for. DLC attaches to the base game. A user who has a deluxe edition can buy these DLC.

get_product returns these four fields by default. Set include_related to false to omit them and make the response smaller.

get_products omits these four fields by default. Set include_related to true to get them. The default is different because the request can hold 20 products, and each product can have many editions and DLC. When you need the DLC for one game, use get_product.

Absent is not empty: when include_related is false, the four fields are absent from the record. An empty dlc array with a dlc_count of 0 means the game has no DLC in the region.

Bundles in the product record

Two bundle-related shapes appear on the product record. They serve different scenarios.

in_bundle — this product is sold as part of a bundle

Present on individual products that are currently in a live bundle. The field carries the bundle's identity, price and pre-rendered pitch copy, so an agent recommending a game can name-drop the bundle without a second tool call.

Fields on in_bundle:

name, slug, url, image
Bundle identity. Use slug to fetch the full bundle detail with get_product.
price, full_price
Bundle current and full price, in the region's currency. For variable-price bundles, price is the entry-tier price.
bundle_type
Pricing model. See below.
bundle_product_count
Total items in the bundle (top tier for variable-price bundles).
minimum_product_count
Smallest tier size for tiered and build-your-own bundles.
variable_price
True for tiered, pay-what-you-want, and build-your-own bundles.
bundle_price_messages
Pre-rendered pitch copy. See below.

bundle_tiers — this product itself is a bundle

Non-null on records whose type is a bundle type. Each tier lists the products unlocked at that tier's price. Higher tiers include everything in lower tiers.

bundle_tiers[].tier_product_count
Number of items unlocked at this tier.
bundle_tiers[].products[]
Items in this tier. Each has slug, name, type, image, drm and full_price. drm is given per item because one bundle can mix DRM platforms. full_price is the item's list price on its own, to compare picks by value; it is null when the item is not sold separately, which is true of about half of all bundle items. There is no current price or discount, because an item you cannot buy alone has neither. Tier items carry no url — look them up by slug.
Pick-and-mix note: for pick-and-mix bundles (bundle_type: build-your-own), products[] is the pool the user picks from at this tier, not what the user receives. The user picks tier_product_count items from this pool.
Pick-and-mix tier semantics: for pick-and-mix bundles (bundle_type: build-your-own), the item pool lives on tier 0 only. Higher tiers return an empty products array — this is not a data bug. Every tier shares the same pool; higher tiers unlock more picks from it. To describe what's in the bundle at any tier, read bundle_tiers[0].products. To describe the tier prices, read every tier's price and tier_product_count.
bundle_tiers[].price
Price required to unlock this tier, in the region's currency.

bundle_type

standard
Fixed price for a fixed set of items.
tiered
Fixed tiers of items, each priced separately. Higher tiers include everything in lower tiers.
pay-what-you-want
Customer names a price above a floor. Usually charity bundles.
build-your-own
Customer picks N items from a curated pool. More items chosen means a lower per-item price. Also known as pick-and-mix.

bundle_price_messages — pre-rendered pitch copy

Every bundle carries five pre-rendered pitch strings, tuned per bundle_type, folded to the region's currency. Use these verbatim instead of synthesising a pitch.

tiny
Minimal one-line pitch. Example: "5 for £4.99".
short
Short summary. Example: "Get 5 games for £4.99".
quantity
Quantity-focused pitch. Example: "Any 5 games for £4.99".
value
Value-focused pitch. Example: "Save 80% with a 5-game pick-and-mix".
long
Full copy listing every tier. Example: "3 for £2.99, 5 for £4.99, or 10 for £8.99".

Sale record

Returned as items of sales[] from list_sales.

sale_slug
Sale slug. Pass to search_products.sale_slug to filter to products in this sale.
sale_name
Human-readable sale name.
product_count
Distinct products in this sale, in the requested region.
min_discount, max_discount
Lowest and highest discount percent across the sale. Bounds, not average.
ends_at
When the sale ends, as Unix seconds. Null when every product in the sale is open-ended.

Sales are sorted by product count descending. An empty array is a valid response — it means no sale is running.

Facet value record

Returned as items of values[] from list_facets. The facet field at the top level of the response says which facet these values belong to.

name
Display name for the user.
slug
Slug form. Pass to the corresponding search_products filter (tag, publisher, franchise, or developer).
count
Distinct products carrying this facet value, in the requested region, under the same availability, stock, price, and release-date defaults as search_products.
url
Canonical facet landing page URL with tracking parameters. Present only when count is at least 5 and the facet has a landing page. tags maps to /en/categories/<slug>, publishers to /en/publishers/<slug>, franchises to /en/franchises/<slug>. Developers have no landing page and never carry a url.

Currency folding

The catalogue stores prices as maps keyed by currency. Before a response leaves the server, every monetary field is folded to a plain value in the region's currency.

Folded paths on the product record:

A folded field is null when the underlying map has no value for the region's currency. A folded pitch string is null when no copy exists for that currency.

Availability is per-country. Currency is derived from country. Two countries in the same currency zone (for example DE and FR) get the same prices but may have different availability.

Reading dates, prices and slugs

Every timestamp is Unix seconds. Not milliseconds. Not an ISO string. Convert with new Date(value * 1000).

Every price is a plain number in the response wrapper's currency. No object, no key. Null when the price is not set for that currency.

Every slug is lowercase, hyphen-separated, ASCII. Slugs come from list_facets, list_sales, or from a prior product record. Never synthesise a slug — a guessed slug that does not exist returns zero results with no error.

Errors and null results

Not-found is a valid response, not an error. get_product returns { region, currency, product: null }. get_products lists the identifiers it could not match in not_found. search_products, list_sales and list_facets return empty arrays in the relevant field.

Real errors return an MCP error response. Two error classes:


Back to /for-ai · Questions to dev@fanatical.com