{"openapi":"3.1.0","info":{"title":"getcheapgames API","description":"Game key price comparison. All money is integer cents; rankings are on the price each store lists, without the service fee a marketplace adds at checkout.","version":"0.1.0"},"paths":{"/api/stores":{"get":{"tags":["catalog"],"summary":"Get Stores","operationId":"get_stores_api_stores_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/StoreOut"},"type":"array","title":"Response Get Stores Api Stores Get"}}}}}}},"/api/stores/stats":{"get":{"tags":["catalog"],"summary":"Get Store Stats","description":"The stores table's figures, over the whole catalogue.\n\nSeparate from `/stores` because the two have different shelf lives and\ndifferent audiences. `/stores` is identity — name, domain, colour — which\nthe footer, the ranking page and the home page all render and which changes\nonly when a store is added; folding per-currency aggregates into it would\nmake every one of those pages pay for numbers it does not show, once per\ncurrency.","operationId":"get_store_stats_api_stores_stats_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StoreStatsOut"},"title":"Response Get Store Stats Api Stores Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stores/{code}":{"get":{"tags":["catalog"],"summary":"Get Store Page","description":"A listed store's page, in the requested market's currency.\n\nAnswers 404 while `catalog.STORE_PAGES_LIVE` is off, exactly as it does for\na code no listed store has: the page is not public until its copy is.","operationId":"get_store_page_api_stores__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorePageOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/games":{"get":{"tags":["catalog"],"summary":"Get Games","operationId":"get_games_api_games_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search term","title":"Q"},"description":"Search term"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Games Api Games Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/filters":{"get":{"tags":["catalog"],"summary":"Get Filters","description":"The facets the sidebar draws its checkboxes from.","operationId":"get_filters_api_filters_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiltersOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deals":{"get":{"tags":["catalog"],"summary":"Get Deals","operationId":"get_deals_api_deals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many rows to skip","default":0,"title":"Offset"},"description":"How many rows to skip"},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Deals Api Deals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/top-sellers":{"get":{"tags":["catalog"],"summary":"Get Top Sellers","description":"The home page's \"Top sellers\": the pinned games, then Steam's weekly top\nsellers we carry and can price (`catalog.top_sellers`). One strip, never\npaged, so it sends no page headers.","operationId":"get_top_sellers_api_top_sellers_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":8,"title":"Limit"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Top Sellers Api Top Sellers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/games/{slug}/history":{"get":{"tags":["catalog"],"summary":"Get Game History","description":"The price chart's data: per store, per platform, per day.\n\nA separate request from the game itself, deliberately. The page can paint\nits answer — title, cheapest total, the offer table — before this arrives,\nand a 90-day series across several stores is several times the size of\neverything else on the page. Folding it into `GET /games/{slug}` would\ndelay the part a buyer came for behind the part they scroll to.","operationId":"get_game_history_api_games__slug__history_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceHistoryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/games/{slug}":{"get":{"tags":["catalog"],"summary":"Get Game","operationId":"get_game_api_games__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":12},{"type":"null"}],"title":"Locale"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stats":{"get":{"tags":["catalog"],"summary":"Get Stats","operationId":"get_stats_api_stats_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__schemas__StatsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/countries":{"get":{"tags":["catalog"],"summary":"Get Countries","description":"Every country we serve, with what it is priced in — drives the selector.","operationId":"get_countries_api_countries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CountryOut"},"type":"array","title":"Response Get Countries Api Countries Get"}}}}}}},"/api/geo":{"get":{"tags":["catalog"],"summary":"Get Geo","description":"Where Cloudflare thinks this visitor is.\n\nRead from `CF-IPCountry`, which the edge sets on every request — no\nthird-party lookup, no extra latency, and nothing about the visitor stored.\nCloudflare answers `XX` for anonymising networks and `T1` for Tor; both\nresolve to the default rather than failing, because a guess is all this\never was.","operationId":"get_geo_api_geo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoOut"}}}}}}},"/api/publishers/lookup":{"get":{"tags":["catalog"],"summary":"Lookup Publisher","description":"ONE publisher by name or any spelling aliased to it, with its logo.\n\nMatched the way the feeds are grouped (`catalog.find_publisher`), and\nunlike `/publishers` it does not need a game we can price: the release\ncards ask for the logo of a studio we may never have sold anything of.\n404 for a name we do not hold; the card kit then adds it with\n`python -m gcg.cli publisher-logo`.\n\nFive minutes in a cache, the logo's own lifetime (`publisher_logos`), so a\nlogo just uploaded shows within the time the file itself would.","operationId":"lookup_publisher_api_publishers_lookup_get","parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"description":"A name or alias","title":"Name"},"description":"A name or alias"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublisherLookupOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/publishers":{"get":{"tags":["catalog"],"summary":"Get Publishers","description":"A page of publishers we can price a game for, ordered 0-9 then A-Z.\n\nFilters narrow both the list and each card's count, so a card that says\nnine games opens onto nine games.\n\nPaged for the reason the franchise index was: this returned every row on\nevery load — 185 of them, each carrying a logo lookup — so the page paid\nfor the whole list to show two dozen tiles.\n\n`limit` stays optional so the sitemap can still ask for every row in one\ncall; the paging headers are only meaningful when it is set.","operationId":"get_publishers_api_publishers_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many rows to skip","default":0,"title":"Offset"},"description":"How many rows to skip"},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublisherOut"},"title":"Response Get Publishers Api Publishers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/franchises":{"get":{"tags":["catalog"],"summary":"Get Franchises","description":"A page of series we can price a game for, the most popular first, or A-Z.\n\nFiltered the same way as the publisher index: a series is listed when a\ngame in it has a matching offer, and the count says how many do.\n\n`limit` stays optional so the sitemap can still ask for every row in one\ncall; the paging headers are only meaningful when it is set.","operationId":"get_franchises_api_franchises_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many rows to skip","default":0,"title":"Offset"},"description":"How many rows to skip"},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"sort","in":"query","required":false,"schema":{"enum":["popular","name"],"type":"string","description":"popular (the default): most played first; name: 0-9 then A-Z","default":"popular","title":"Sort"},"description":"popular (the default): most played first; name: 0-9 then A-Z"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FranchiseOut"},"title":"Response Get Franchises Api Franchises Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/publishers/{slug}":{"get":{"tags":["catalog"],"summary":"Get Publisher Games","description":"One publisher's games, a page at a time — and, in `X-Publisher`, the\npublisher itself.\n\nThe page for a publisher needs its name, its logo and how many games it\nhas, and the only place it could get them was `/publishers` with no limit:\nevery publisher there is, 12,000 rows and 1.1 MB, to find one. They ride\non this response instead.\n\nA header rather than a field, for the reason the paging facts are headers\n(`_page`): the body is a plain list, and wrapping it to add one would\nchange its shape under every caller, the WebMCP tools included. The value\nis `PublisherOut` as JSON, the same shape the index returns, ASCII-escaped\nbecause a header must be — publisher names are written in every script.\nIts `games` is the unfiltered count the index shows, not `X-Total`: it is\nwhat decides whether the page may be indexed, and ticking a filter must not\nchange that.","operationId":"get_publisher_games_api_publishers__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":48,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Publisher Games Api Publishers  Slug  Get"}}},"headers":{"X-Publisher":{"description":"This publisher as the index lists it (`PublisherOut`), as ASCII JSON: slug, name, games (unfiltered), logo, logoDark.","schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/franchises/{name}":{"get":{"tags":["catalog"],"summary":"Get Franchise Games","operationId":"get_franchise_games_api_franchises__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":48,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Franchise Games Api Franchises  Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/franchises/{name}/story":{"get":{"tags":["catalog"],"summary":"Get Franchise Story","description":"A series' cover as its page shows it — its timeline where one is drawn\n(`franchise_covers`), else its first game's — and, for the few with a\nprofile (`franchise_profiles`), the year it began and its story. 404 for\na name no game carries.\n\nThe story changes only with a deploy and the cover when a game joins the\nseries, so ten minutes in a browser's cache costs nothing.","operationId":"get_franchise_story_api_franchises__name__story_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FranchiseStoryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-to-play/filters":{"get":{"tags":["catalog"],"summary":"Get Free To Play Filters","description":"The sidebar for the free-to-play page: only what its games have. No stores\nand no regions — those describe keys someone sells, and every game here is\nfree from Steam itself.","operationId":"get_free_to_play_filters_api_free_to_play_filters_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiltersOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-to-play":{"get":{"tags":["catalog"],"summary":"Get Free To Play","description":"Every game Steam gives away, most reviewed first, narrowed as `/deals` is.","operationId":"get_free_to_play_api_free_to_play_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many rows to skip","default":0,"title":"Offset"},"description":"How many rows to skip"},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"pinned","in":"query","required":false,"schema":{"type":"boolean","description":"false: no pinned (possibly partner-paid) places first","default":true,"title":"Pinned"},"description":"false: no pinned (possibly partner-paid) places first"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameCardOut"},"title":"Response Get Free To Play Api Free To Play Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/find":{"get":{"tags":["catalog"],"summary":"Find","description":"The best-rated paid games in the genres and on the platforms asked for,\nwith a key in stock at `max_price` or less: \"a strategy game under €20\",\nfor the MCP server's `find_games` and the home page's example chat\n(`catalog.find_games`). Each with Steam's review summary it is ranked by.","operationId":"find_api_find_get","parameters":[{"name":"max_price","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100000,"exclusiveMinimum":0},{"type":"null"}],"description":"The highest price, in `currency` (e.g. 20 for €20)","title":"Max Price"},"description":"The highest price, in `currency` (e.g. 20 for €20)"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":5,"title":"Limit"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FoundGameOut"},"title":"Response Find Api Find Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/giveaways":{"get":{"tags":["catalog"],"summary":"Get Giveaways","description":"What Steam, the Epic Games Store and GOG give away right now, free to keep\nif claimed before it ends, for the top of the free-to-play page.","operationId":"get_giveaways_api_giveaways_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; its currency is used","title":"Country"},"description":"ISO country code; its currency is used"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__schemas__GiveawaysOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/giveaways/count":{"get":{"tags":["catalog"],"summary":"Get Giveaway Count","description":"How many games are free to keep right now, for the badge on the header's\nfree-to-play button: a number, so every page does not fetch the list.","operationId":"get_giveaway_count_api_giveaways_count_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiveawayCountOut"}}}}}}},"/api/search":{"get":{"tags":["catalog"],"summary":"Search Everything","description":"One term, three kinds of answer.\n\n\"team\" is a game word, a franchise word and a publisher name at once, and a\nvisitor typing it means any of them. Splitting the answer lets the page say\nwhich is which instead of ranking incomparable things against each other.","operationId":"search_everything_api_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search term","title":"Q"},"description":"Search term"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}},{"name":"on_sale","in":"query","required":false,"schema":{"type":"boolean","description":"Only offers marked down by their store","default":false,"title":"On Sale"},"description":"Only offers marked down by their store"},{"name":"stores","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Store codes","title":"Stores"},"description":"Store codes"},{"name":"platforms","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Canonical platforms","title":"Platforms"},"description":"Canonical platforms"},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Steam genre descriptions","title":"Genres"},"description":"Steam genre descriptions"},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Offer regions","title":"Regions"},"description":"Offer regions"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"game, dlc, pass, soundtrack or free; anything else is ignored","title":"Types"},"description":"game, dlc, pass, soundtrack or free; anything else is ignored"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/subscribe":{"post":{"tags":["alerts"],"summary":"Post Subscribe","description":"Email a confirmation link for the list the body names: `sent` whatever the\naddress's state, and whatever Resend answers (a refused send waits for\nthe next pass). `queued` only while no Resend key is set, for every\naddress alike, and the signup waits for the next giveaway pass that can\nsend it.","operationId":"post_subscribe_api_alerts_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertSubscribeIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/confirm":{"post":{"tags":["alerts"],"summary":"Post Confirm","description":"The Confirm button on the page the confirmation email links to:\n`confirmed`, `expired` (ours, older than `alerts.CONFIRM_TTL`) or\n`invalid`, with the list the link was for.","operationId":"post_confirm_api_alerts_confirm_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","maxLength":200,"title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/unsubscribe":{"post":{"tags":["alerts"],"summary":"Post Unsubscribe","description":"The Unsubscribe button, and a mail client's own one (RFC 8058), which\nposts `List-Unsubscribe=One-Click` to this address with the token in it.\nA confirmation's \"Remove this address\" lands here too, for a signup not\nyet confirmed.","operationId":"post_unsubscribe_api_alerts_unsubscribe_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","maxLength":200,"title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/consent":{"post":{"tags":["consent"],"summary":"Record Consent","description":"Keep one cookie decision. 204 and nothing else: the site does not wait.\n\nThe body is parsed by hand rather than as a typed parameter so it is read\nwhatever content type it comes with — a keepalive `fetch` sends JSON, and\n`navigator.sendBeacon` with a string sends `text/plain` — and so its size\nis capped before it is parsed.\n\nRefused with 403 when the browser says the call came from another site\n(`Sec-Fetch-Site`): a page elsewhere must not be able to write consent\nrecords through its visitors' browsers. Older browsers send no such header\nand are let through; the most a forger gets is a row that names a random\nid nobody holds, and no visitor's own answer (in their browser) changes.","operationId":"record_consent_api_consent_post","responses":{"204":{"description":"Successful Response"}}}},"/api/auth/providers":{"get":{"tags":["auth"],"summary":"Providers","description":"Which ways in this deployment offers. Answered with accounts off too,\nunlike everything else here: `accounts: false` is how the app knows to\ndraw no sign-in at all, rather than a 503 it would have to read as that.","operationId":"providers_api_auth_providers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvidersOut"}}}}}}},"/api/auth/register":{"post":{"tags":["auth"],"summary":"Register","description":"A new account from an address and a password: `sent` for every address\n(module docstring). 422 `invalid-email` or `invalid-password` only for\nwhat was typed, never for what the address has. A new address is signed\nin beside the `sent` (the cookies on this answer; the dialog then asks\n`/account/me`); a taken one is not, and its owner gets the reset email.","operationId":"register_api_auth_register_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/auth/password":{"post":{"tags":["auth"],"summary":"Sign In Password","description":"Signing in with the address and the password: `ok` with the session set,\nor `totp` with the challenge the code step hands back. 401\n`wrong-password` for anything that is not a match, alike for every way\nof not being one. An unconfirmed address signs in too: the wishlist works\nbefore the confirmation (en.ts `auth.sentVerify`), the alerts do not.","operationId":"sign_in_password_api_auth_password_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__accounts__schemas__SignInOut"}}}}}}},"/api/auth/totp":{"post":{"tags":["auth"],"summary":"Sign In Totp","description":"The second step of a sign-in: the challenge `/password` or `/reset`\nanswered (or, after a provider, the one in the challenge cookie), and a\ncode from the app or a recovery code. 410 `challenge-expired` for a\nsign-in to start again, 401 `wrong-code`, 429 past the account's five\ntries in fifteen minutes, counted against the account the challenge is\nfor, whichever browser tries.","operationId":"sign_in_totp_api_auth_totp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__accounts__schemas__SignInOut"}}}}}}},"/api/auth/forgot":{"post":{"tags":["auth"],"summary":"Forgot Password","description":"A reset link for an address: `sent` for every address, mailed only to\none with an account, and at most three times an hour to any one\naddress. An account with no password yet gets one this way (en.ts\n`account.setPasswordBody`).","operationId":"forgot_password_api_auth_forgot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/auth/reset":{"post":{"tags":["auth"],"summary":"Reset Password","description":"A reset link's new password: set, the address confirmed, every session\nended, and this browser signed in — or `totp` with a challenge, the\npassword set either way. `expired` or `invalid` (used, or overtaken by\na change of password or address) for a dead link, checked before the\npassword, so a dead link is not met with a password rule.","operationId":"reset_password_api_auth_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/gcg__accounts__schemas__SignInOut"},{"$ref":"#/components/schemas/StatusOut"}],"title":"Response Reset Password Api Auth Reset Post"}}}}}}},"/api/auth/verify":{"post":{"tags":["auth"],"summary":"Verify Email","description":"The verification link's button: the address confirmed, nobody signed\nin. `ok` again for an address already confirmed — a second press, or\nthe link opened after a day when the first press had done it —\n`expired` for a day-old link to one still unconfirmed, else `invalid`\n(not ours, or about an address the account no longer has).","operationId":"verify_email_api_auth_verify_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/auth/verify/resend":{"post":{"tags":["auth"],"summary":"Resend Verification","description":"The verification email again, for the signed-in account's own address:\n`sent` whatever happened, as the throttle (ten minutes, five a day) or\na Remove may have kept it — the account page says \"check your inbox\"\neither way, and nothing here is about anybody else's address.","operationId":"resend_verification_api_auth_verify_resend_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/auth/remove":{"post":{"tags":["auth"],"summary":"Remove Unverified","description":"\"Remove this account\", from the verification email: the page's button,\nor a mail client's own one-click unsubscribe (RFC 8058) posting\n`List-Unsubscribe=One-Click`, whose body is not read. No same-origin\ncheck, as that POST comes from the mail provider: the token is the\nauthority. Deletes the account only while its address is the one the\nemail went to and is still unconfirmed, and keeps that address from\nbeing mailed for thirty days. `ok`, or `invalid` for anything else,\nan account since confirmed included.","operationId":"remove_unverified_api_auth_remove_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/auth/logout":{"post":{"tags":["auth"],"summary":"Sign Out","description":"This browser's session ended and both cookies cleared — and a pending\nsecond factor's cookie with them. 204 whether or not there was a\nsession: a sign-out asked for is done either way.","operationId":"sign_out_api_auth_logout_post","responses":{"204":{"description":"Successful Response"}}}},"/api/auth/logout-all":{"post":{"tags":["auth"],"summary":"Sign Out Everywhere","description":"Every other session of the account ended; this browser's stays. 401 without one.","operationId":"sign_out_everywhere_api_auth_logout_all_post","responses":{"204":{"description":"Successful Response"}}}},"/api/auth/{provider}/start":{"post":{"tags":["auth"],"summary":"Start","description":"Begin a sign-in or a link: write the flow down, bind it to this browser, go.","operationId":"start_api_auth__provider__start_post","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/{provider}/callback":{"get":{"tags":["auth"],"summary":"Callback","description":"Where the provider sends the browser back: finish the flow, then land on the app.","operationId":"callback_api_auth__provider__callback_get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/me":{"get":{"tags":["account"],"summary":"Get Me","description":"The signed-in account: what lib/session.ts asks for at boot, and the account page reads.","operationId":"get_me_api_account_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeOut"}}}}}}},"/api/account/username":{"post":{"tags":["account"],"summary":"Post Username","description":"A new username: the account as it now is, or 422 `invalid-username` for\nits shape and 409 `username-taken` for one in use (any case) or reserved.","operationId":"post_username_api_account_username_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeOut"}}}}}}},"/api/account/platforms":{"post":{"tags":["account"],"summary":"Post Platforms","description":"\"My platforms\", all of them at once: `{platforms: [...]}`, any of\ngcg.platforms.FAMILIES' four, the empty list for every platform. The\naccount as it now is, as `POST /username` answers, so the page that\nsaved it hands it to lib/session.ts and the listings follow at once.\n\nKept each once and in FAMILIES' order, however it was sent. 422\n`invalid-platform` for anything but a list of the four's exact names,\nand nothing is saved: a client that sent \"ps5\" meant a choice, and\nsaving the rest of the list would answer it with a different one.\n\nNo password and no code: this changes what the account is shown, not\nhow it is reached. The most a stolen cookie does with it is make the\nowner's choice for them, which the owner sees, and undoes, on the\naccount page.","operationId":"post_platforms_api_account_platforms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeOut"}}}}}}},"/api/account/notifications":{"get":{"tags":["account"],"summary":"Get Notifications","description":"The account page's email switches, and the address they are about.","operationId":"get_notifications_api_account_notifications_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationsOut"}}}}}},"post":{"tags":["account"],"summary":"Post Notifications","description":"One switch, `{key, on, cc}`: `key` one of `NOTIFICATION_KEYS`, `on` true\nor false, `cc` the storefront the page is on, which a list joined here\nthen writes its emails for. The switches as they now are, as `GET`\nanswers them, so the page shows what was saved.\n\nA list's switch is the address's row on it, the very one its emails'\nlinks change (`alerts.join_confirmed`, `alerts.leave`): on is on the\nlist at once, with no confirmation email — the account's own\nverification was one — and off is the unsubscribe the email's link\nmakes. The price alerts' is `User.price_alerts`: off holds the alerts and\nkeeps every target.\n\n409 `verify-first` without a confirmed address: there is no inbox yet to\nspeak for (en.ts says which is missing, from `GET`). 422\n`invalid-notification` for a key that is not one, or an `on` that is not\na boolean, and nothing changes. No password and no code, as `POST\n/platforms`: this changes what the inbox is sent, not how the account is\nreached, and the owner sees the switch and turns it back.","operationId":"post_notifications_api_account_notifications_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationsOut"}}}}}}},"/api/account/email":{"post":{"tags":["account"],"summary":"Post Email","description":"Ask for a new address: a confirmation goes to it, and nothing changes\nuntil its button is pressed (`/email/confirm`). The password where the\naccount has one, a current code where the second factor is on.\n\n`sent` whatever became of it — the address free and mailed, another\naccount's, the account's own, declined, or over the throttle — as en.ts\n`account.emailChangeSent` words it (\"If that address is free…\"): this\nform must not be a way to ask which addresses have accounts, password\nor not. The one place `taken` is said is the confirmation, to whoever\nholds the link, when the address has been claimed since.","operationId":"post_email_api_account_email_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/account/email/confirm":{"post":{"tags":["account"],"summary":"Post Email Confirm","description":"The new inbox's link: `ok` with the address changed and confirmed,\n`taken` when another account has claimed it since it was asked for,\n`expired` after a day, `invalid` for anything else — a used link\nincluded, as the token was signed under the address it replaces.\n\nNo session needed: the link may well be opened on another device, and\nits token is the authentication, as every mail link's is. The address\nthe account had hears of the change (`compose_changed`) when it was a\nconfirmed one: an unconfirmed address may be a stranger's.","operationId":"post_email_confirm_api_account_email_confirm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/account/password":{"post":{"tags":["account"],"summary":"Post Password","description":"A new password, for the current one (and a current code where the\nsecond factor is on). Every other session ends — whoever else was in is\nout — and this one stays. The address hears of it, when confirmed.\n\n409 `no-password` for an account without one: a first password is set\nfrom the reset link (module docstring), which proves the inbox.","operationId":"post_password_api_account_password_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/totp/start":{"post":{"tags":["account"],"summary":"Post Totp Start","description":"Begin an enrolment: a new seed, as a QR code, a key to type and an\notpauth URI, and the sealed blob `/totp/confirm` hands back with the\nfirst code. Nothing is stored until then, and the blob lasts ten minutes.","operationId":"post_totp_start_api_account_totp_start_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpStartOut"}}}}}}},"/api/account/totp/confirm":{"post":{"tags":["account"],"summary":"Post Totp Confirm","description":"The first code from the app turns the second factor on, and the eight\nrecovery codes come back this once. 410 `setup-expired` for a blob over\nten minutes old, altered, or another account's; 401 `wrong-code`.","operationId":"post_totp_confirm_api_account_totp_confirm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodesOut"}}}}}}},"/api/account/totp/disable":{"post":{"tags":["account"],"summary":"Post Totp Disable","description":"The second factor off, for a code from the app or a recovery code (the\nphone may be what was lost), and the password where there is one. The\nseed and every recovery code go with it.","operationId":"post_totp_disable_api_account_totp_disable_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/totp/recovery":{"post":{"tags":["account"],"summary":"Post Totp Recovery","description":"Eight new recovery codes for a current code from the app — not for a\nrecovery code, which would let one leaked code mint eight more. Every\nold one stops working.","operationId":"post_totp_recovery_api_account_totp_recovery_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodesOut"}}}}}}},"/api/account/sessions":{"get":{"tags":["account"],"summary":"Get Sessions","description":"The account's live sessions, newest first, this browser's flagged. Dates\nand nothing else: no device, no address was ever kept to show.","operationId":"get_sessions_api_account_sessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SessionOut"},"type":"array","title":"Response Get Sessions Api Account Sessions Get"}}}}}}},"/api/account/sessions/revoke":{"post":{"tags":["account"],"summary":"Post Sessions Revoke","description":"End one of the account's sessions. This browser's own is a sign-out, so\nits cookies go too. An id that is not the account's — another's, or one\nalready ended elsewhere — ends nothing and is answered the same, so the\nlist on another tab may be a little stale without an error.","operationId":"post_sessions_revoke_api_account_sessions_revoke_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/unlink":{"post":{"tags":["account"],"summary":"Post Unlink","description":"Take a provider off the account. 409 `last-method` when it is the only\nway in that would be left: no other provider, and no password with an\naddress to type it with. A provider not linked is nothing to do.","operationId":"post_unlink_api_account_unlink_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/export":{"post":{"tags":["account"],"summary":"Post Export","description":"The account's data as a JSON file to save (`ExportOut`), camelCase like\nevery answer here. A POST, as lib/api.ts asks for it: a per-user file is\nnot something a link, a prefetch or another site's `<img>` should fetch.\nThe wishlist is read from its tables directly, with each game's slug\nand title, and every alert that went out for it; the reviews likewise,\neach with the slug and title of the game it is kept on; the email\nswitches exactly as the account page shows them (`_notifications`).","operationId":"post_export_api_account_export_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/account/delete":{"post":{"tags":["account"],"summary":"Post Delete","description":"The account gone, and everything it owns with it: every table that\nholds a row of the account's references it `ON DELETE CASCADE`\n(migration 0079), so one DELETE takes the sessions, the sign-ins, the\nrecovery codes, the wishlist and its alert history. `DELETE` typed to\nconfirm (422 `confirm-delete`), and the password where there is one.\nThis browser's cookies are cleared in the answer.\n\nThe giveaway alerts and the weekly email are not the account's (en.ts\n`account.deleteLists`): each has its own unsubscribe link. News from\ngetcheapgames has none yet, and only this account could join it, so the\naddress leaves it with the account.","operationId":"post_delete_api_account_delete_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/wishlist":{"get":{"tags":["wishlist"],"summary":"Get Wishlist","description":"Every item, newest first, each priced for its own market.","operationId":"get_wishlist_api_account_wishlist_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WishlistOut"}}}}}}},"/api/account/wishlist/refs":{"get":{"tags":["wishlist"],"summary":"Get Refs","description":"The hearts' shape: no prices, no cards, one small query.","operationId":"get_refs_api_account_wishlist_refs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WishlistRefsOut"}}}}}}},"/api/account/wishlist/add":{"post":{"tags":["wishlist"],"summary":"Post Add","description":"The game on the wishlist, in the market and money the visitor is on, or\nthe item it already is: pressing the heart twice, or on two devices at\nonce, makes one item and changes nothing. A target here is \"Set alert\"\non a game the page took for not wishlisted; when it already was (added\non another device meanwhile), the alert asked for is set on it all the\nsame, as `/target` would, rather than dropped without a word.","operationId":"post_add_api_account_wishlist_add_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WishlistItemOut"}}}}}}},"/api/account/wishlist/target":{"post":{"tags":["wishlist"],"summary":"Post Target","description":"Set the alert to `targetCents` in `currency`, or clear it (null); the game\nstays either way. The currency is the target's, so the item is priced in\nit from now on.","operationId":"post_target_api_account_wishlist_target_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WishlistItemOut"}}}}}}},"/api/account/wishlist/remove":{"post":{"tags":["wishlist"],"summary":"Post Remove","description":"Off the wishlist, with its alert history. 204 for an item already gone,\nso a double click is not an error; another account's is never touched.","operationId":"post_remove_api_account_wishlist_remove_post","responses":{"204":{"description":"Successful Response"}}}},"/api/account/wishlist/mute":{"post":{"tags":["wishlist"],"summary":"Post Mute","description":"A price alert's \"Stop alerts\" (one game) or \"Stop all price alerts\": the\nmute page's button, and a mail client's one-click unsubscribe, which posts\n`List-Unsubscribe=One-Click` here with the token in the address (RFC 8058;\nno same-origin check, as the token is the authority). `ok` for any token\nof ours, `invalid` for anything else. The token is read from the query by\nhand, so that no validation error ever repeats it.","operationId":"post_mute_api_account_wishlist_mute_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusOut"}}}}}}},"/api/games/{slug}/reviews":{"get":{"tags":["reviews"],"summary":"Get Reviews","description":"The GCG rating and a page of the visible reviews, newest first.","operationId":"get_reviews_api_games__slug__reviews_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many reviews to skip","default":0,"title":"Offset"},"description":"How many reviews to skip"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"How many to return","default":10,"title":"Limit"},"description":"How many to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewPageOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/reviews/{slug}":{"get":{"tags":["reviews"],"summary":"Get My Review","description":"The user's own review of the product, hidden or not, or null: what the\nform opens with. An edition answers with the review of its game, where\nit is kept.","operationId":"get_my_review_api_account_reviews__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/MyReviewOut"},{"type":"null"}],"title":"Response Get My Review Api Account Reviews  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/reviews":{"post":{"tags":["reviews"],"summary":"Post Review","description":"The review written, or the one the user has rewritten: stars and text\nboth replaced by what was sent (null text is stars alone). Checked in\nthe order the form can act on: what was typed (422), then whether this\naccount may post (403), then how often it has (429), then the product\n(404). A refused post saves nothing.","operationId":"post_review_api_account_reviews_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewChangeOut"}}}}}}},"/api/account/reviews/delete":{"post":{"tags":["reviews"],"summary":"Post Delete","description":"The user's review of the product gone, and the rating without it. The\nsame answer when there was none; another account's is never touched.\nOpen to any signed-in author, confirmed or not: taking one's own words\ndown is never refused for want of a check.","operationId":"post_delete_api_account_reviews_delete_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewChangeOut"}}}}}}},"/api/news":{"get":{"tags":["news"],"summary":"List News","description":"Live posts, newest first, `news.PAGE_SIZE` to a page; a page past the last\nis empty. Each in `locale` where it has a current translation into it.","operationId":"list_news_api_news_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"From 1","default":1,"title":"Page"},"description":"From 1"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"enum":["news","guide","review","deals","video"],"type":"string"},{"type":"null"}],"description":"Only posts filed under this","title":"Tag"},"description":"Only posts filed under this"},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The reader's language, as the app names it","title":"Locale"},"description":"The reader's language, as the app names it"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/news/{slug}":{"get":{"tags":["news"],"summary":"Get News Post","description":"One live post: its body as blocks, its games priced in `currency`, three\nmore to read; the post and those three each in `locale` where it has a\ncurrent translation into it. A translated body's mentions are priced as\nthe English's are: the same games, by the slugs it names them with.","operationId":"get_news_post_api_news__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","maxLength":120,"title":"Slug"}},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The reader's language, as the app names it","title":"Locale"},"description":"The reader's language, as the app names it"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO country code; decides which keys are region-locked FOR YOU","title":"Country"},"description":"ISO country code; decides which keys are region-locked FOR YOU"},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ","title":"Currency"},"description":"One of EUR, GBP, USD, CAD, CHF, DKK, SEK, NOK, PLN, CZK, HUF, BRL, MXN, CLP, COP, PEN, CRC, UYU, GTQ"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/game/{slug}":{"get":{"tags":["outbound"],"summary":"Outbound Game","description":"A buy link that does not go stale: a game, optionally a store, and a market.\n\nFor links that leave this site and live on somewhere else — an AI\nassistant's answer, a chat, a saved note — and are clicked days later. An\noffer's own `/api/out/{id}` names one listing, and a listing can be\nre-regioned or withdrawn; this names what the reader wanted and resolves it\nwhen they click: the cheapest in-stock offer (at `store`, when given) that\nwill activate in `country`, chosen by `_rankable`, the rule the page and the\ncrawler shell price with. Then it goes through the same interstitial and\n`/go` as every other click, so the affiliate tag and the click record are\nexactly the ones a click on the page produces.\n\nRegion-locked keys are left out, which the page's headline price does not\ndo: the page shows the warning beside the price, and a link followed from a\nchat shows nothing until the key fails to activate.\n\nNothing left to sell there sends the reader to the game's page, which is\nours and lists who does; an unknown game to the market's home page.","operationId":"outbound_game_api_out_game__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"store","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Store"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Country"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/badge/{slug}.svg":{"get":{"tags":["outbound"],"summary":"Price Badge","description":"A game's cheapest price as an image, for other sites to embed.\n\nThe site's backlinks: a blog, a stream overlay or a forum post shows it\nlinked to the game's page here (the snippet is on every product page), and\nthe price stays true because it is drawn when fetched — the cheapest key\nthat activates in `country`, the same offer `/api/out/game/…` sends a buyer\nto. Only text we escaped goes in, and an `<img>` runs no script besides.\n\nIn the market's language (`lang=` overrides it, any of the site's 25) and\nmoney as that market writes it. \"−X% vs Steam\" only where Steam sells the\ngame in that currency for more. `theme=dark|light`, `size=sm|md`. An\nunknown game or one with no key to sell draws a neutral \"Compare key\nprices\" badge, never a made-up price, rather than a broken image on\nsomebody else's page.","operationId":"price_badge_api_badge__slug__svg_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Country"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Lang"}},{"name":"theme","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Theme"}},{"name":"size","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":4},{"type":"null"}],"title":"Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/{offer_id}":{"get":{"tags":["outbound"],"summary":"Outbound Interstitial","description":"The branded page a buyer sees on the way to a store.\n\nIt deliberately does NOT log the click and does NOT know the destination.\nBoth belong to `/go` below, and splitting them is what makes the page safe\nto show:\n\n* The tagged URL stays server-side. Putting it in this document would undo\n  the reason the redirect exists at all — `build_affiliate_url` runs on the\n  server so the affiliate id is never somewhere it can be read off the page\n  or stripped by an extension.\n* A click means a buyer who actually left. Anything that merely FETCHES\n  this page — a crawler, a link preview, a browser prefetching on hover —\n  gets HTML and stops there. Under the old single-endpoint design every one\n  of those wrote an `outbound_clicks` row indistinguishable from a sale.\n\nSet `GCG_INTERSTITIAL_DELAY_MS=0` to bypass it entirely and go straight to\nthe store. This sits between a buyer and a purchase, so it needs a kill\nswitch that does not require a deploy.","operationId":"outbound_interstitial_api_out__offer_id__get","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"integer","title":"Offer Id"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/{offer_id}/go":{"get":{"tags":["outbound"],"summary":"Outbound","description":"Redirect to a store, tagged with our affiliate identity.\n\nServer-side for three reasons: the affiliate id never reaches the browser,\nthe click is logged even when an extension would have blocked a beacon, and\nthe ids can be rotated without shipping a new frontend.\n\n302, not 301 — the destination changes whenever the affiliate id or the\nstore's product URL does, and a permanent redirect would be cached past\nthat.\n\n`c` is the click id the interstitial minted (`_go_url`): the same page\nreaching here twice is one row, and the network sees one sub-id. A\nprefetch is refused before anything is written (`is_prefetch`).","operationId":"outbound_api_out__offer_id__go_get","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"integer","title":"Offer Id"}},{"name":"c","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"C"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/promo/{slug}":{"get":{"tags":["outbound"],"summary":"Promo Interstitial","description":"The coupon handed over on the way out of a banner.\n\nA banner used to be a bare 302, and the code it applies to sat in a strip\non the home page — which meant the page gave away the one thing worth\nclicking for before anybody clicked. The code now lives here, on the way\nout, where it is worth something to both sides.\n\nThis half does NOT redirect and does NOT know the destination, exactly as\nthe offer interstitial does not: `/go` owns both. That split is what keeps\nthe tracked URL server-side, and it is why a crawler or a link preview that\nmerely FETCHES this page gets HTML and stops there.\n\nFalls straight through to the redirect when there is no live code for the\nstore. A scratch card over an empty promise is worse than no scratch card,\nand it would put a page between a reader and a shop for nothing.","operationId":"promo_interstitial_api_out_promo__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/promo/{slug}/go":{"get":{"tags":["outbound"],"summary":"Outbound Promo","description":"A banner's own destination, kept off the page.\n\nThe creative's tracked URL carries our publisher hash, so it lives in\nsettings for the same three reasons every other affiliate id does: it never\nreaches the browser, an extension cannot strip it, and rotating it does not\nneed a frontend build.\n\nFalls back to the store's front page when the destination is unconfigured.\nA banner that lands one level too high still earns; one that 404s does not,\nand an empty setting must not take the home page down with it.","operationId":"outbound_promo_api_out_promo__slug__go_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"c","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"C"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/store/{code}":{"get":{"tags":["outbound"],"summary":"Outbound Store","description":"Redirect to a store's own front page, tagged where we can tag it.\n\nThe footer lists every store we compare, and those names are now links —\na reader who wants to look at Gamivo directly should not have to retype it.\nRouting them through here rather than linking the domain straight keeps the\naffiliate id server-side, exactly as the per-offer redirect does, and means\na programme we activate later starts earning without a frontend change.\n\nNo interstitial: that page exists to explain a PRICE we are handing off,\nand there is no price here. The click IS recorded, as placement 'store'\nwith no offer, game or price (migration 0063): these are tagged affiliate\nexits, and leaving them out meant the networks always counted more than\nthe dashboard did — the audit found one in two days of logs, against six\ncounted clicks.\n\n302 rather than 301 for the same reason as the offer redirect: the tagged\ndestination changes whenever the affiliate id does.\n\nTwo ways to tag, and which one applies is a property of the programme.\n`tag_url` handles the stores whose own URL takes a parameter — G2A's\n`gtag`. A NETWORK programme cannot work that way: the tracked link is the\nnetwork's, and the shop's url goes inside it. Kinguin runs on Admitad, so\nits front-page link is a configured gotolink rather than anything derived\nfrom `store.domain`.\n\nThat value is a setting and NOT an `affiliate_wrap` on the store row, which\nis the obvious-looking fix and would break the site — see the comment on\n`store_url_kinguin`. A wrap applies to every url for the store, and Kinguin\noffer urls come from the feed already tagged.","operationId":"outbound_store_api_out_store__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/out/partner/ig":{"get":{"tags":["outbound"],"summary":"Outbound Instant Gaming","description":"The Instant Gaming banner's hop, routed through us like every other.\n\nThree path segments, like `/out/promo/...` and `/out/store/...`, and not\nthe tidier `/out/ig`: that is two segments and would be matched by\n`/out/{offer_id}` above, which is declared first and types the id as an\nint — so the tidier spelling is a 422 on every click. Depending on\ndeclaration order instead would work until somebody reorders the file.\n\nTheir widget builds its own anchor in the browser, pointing straight at\ntheir site — so unlike every other advert here the click never touched our\norigin, and nothing we measure could see it. The frontend rewrites that\nanchor to this endpoint and hands the destination along.\n\nThe destination is carried rather than configured because the creative\nrotates: it was advertising FC 27 when this was written, and sending that\nclick to the front page instead of the page the picture promised is the\nkind of tidy-looking change that quietly costs conversions.\n\nTwo things are enforced rather than trusted. The host must be theirs, or\nthis is an open redirect wearing our domain. And the partner id is\nOVERWRITTEN with ours, so a hand-edited link cannot point our traffic at\nsomebody else's account — which is the one thing a caller could otherwise\nchange to their benefit and our cost.\n\nRecorded like every other exit, as placement 'partner' with no store row\n(Instant Gaming is not a store we compare), tagged when our partner id\nwent on.","operationId":"outbound_instant_gaming_api_out_partner_ig_get","parameters":[{"name":"to","in":"query","required":false,"schema":{"type":"string","default":"","title":"To"}},{"name":"src","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":48},{"type":"null"}],"title":"Src"}}],"responses":{"307":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/postback/mylead":{"get":{"tags":["postback"],"summary":"Mylead","description":"Record one report on a sale: the first creates it, the next ones move its\nstatus and carry what changed.\n\n404 to a wrong or missing key, or a call MyLead did not sign, as to a page\nthat does not exist: nothing here should tell a stranger the address is\nreal. Both compared in constant time.\n\nThe signature is over the url as MyLead called it, so it is rebuilt from\nthe site's own address rather than the request's: Cloudflare and Caddy\nstand between, and the api sees neither MyLead's scheme nor its host.","operationId":"mylead_api_postback_mylead_get","parameters":[{"name":"key","in":"query","required":false,"schema":{"type":"string","default":"","title":"Key"}},{"name":"tx","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tx"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"payout","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payout"}},{"name":"currency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"}},{"name":"program","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Program"}},{"name":"program_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Program Name"}},{"name":"goal","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Goal"}},{"name":"click","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Click"}},{"name":"cart","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cart"}},{"name":"cart_original","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cart Original"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ingest":{"post":{"tags":["admin"],"summary":"Ingest Offers","description":"Accept a batch of scraped observations.\n\n⚠️ Unauthenticated. This must not be exposed publicly — bind it to the\nprivate interface, or put an API key in front of it, before deploying.\nAnyone who can reach this can rewrite every price on the site.","operationId":"ingest_offers_api_admin_ingest_post","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/IngestOfferIn"},"type":"array","title":"Observations"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResultOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/maintenance/partitions":{"post":{"tags":["admin"],"summary":"Create Partitions","operationId":"create_partitions_api_admin_maintenance_partitions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response Create Partitions Api Admin Maintenance Partitions Post"}}}}}}},"/api/admin/maintenance/prune":{"post":{"tags":["admin"],"summary":"Prune","operationId":"prune_api_admin_maintenance_prune_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response Prune Api Admin Maintenance Prune Post"}}}}}}},"/api/admin/campaigns":{"get":{"tags":["admin"],"summary":"Campaigns","description":"Landings from tagged links per day, source and campaign (people, bots, clients\nwith no JavaScript) beside the store clicks each campaign sent on. Not public:\nCaddy refuses /api/admin/* from the internet.","operationId":"campaigns_api_admin_campaigns_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":14,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Campaigns Api Admin Campaigns Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/state":{"get":{"tags":["social"],"summary":"State","operationId":"state_api_admin_social_state_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__social__StateOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/queue":{"get":{"tags":["social"],"summary":"Queue","description":"Deliveries come with each post — the unit is the post.\n\nThe desk's two tabs split the posts in two, and every post is in exactly\none. `posted`: it went out on at least one network, the newest out first.\n`scheduled`: everything else, whether waiting, failed or cancelled, with\nthe next one due first. A post out on one network and still waiting on\nanother is `posted`, and its row says what is still to come. `all` is\nnewest first.","operationId":"queue_api_admin_social_queue_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200,"default":"","title":"Q"}},{"name":"view","in":"query","required":false,"schema":{"enum":["all","scheduled","posted"],"type":"string","default":"all","title":"View"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/settings":{"patch":{"tags":["social"],"summary":"Patch Settings","operationId":"patch_settings_api_admin_social_settings_patch","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/accounts/{network}":{"patch":{"tags":["social"],"summary":"Patch Account","operationId":"patch_account_api_admin_social_accounts__network__patch","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__social__AccountOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/oauth/{network}/start":{"post":{"tags":["social"],"summary":"Oauth Start","description":"Begin connecting an account: returns the network's approval page.\n\nPOST, not GET: it writes the one-time state the callback is checked\nagainst, and a GET that writes is one a prefetcher can fire.","operationId":"oauth_start_api_admin_social_oauth__network__start_post","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/accounts/{network}/disconnect":{"post":{"tags":["social"],"summary":"Disconnect","operationId":"disconnect_api_admin_social_accounts__network__disconnect_post","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__social__AccountOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/posts":{"post":{"tags":["social"],"summary":"Create Post","description":"A post written by hand.\n\nRefused up front for anything the worker would only fail on later — an\nunknown network, one that cannot take this media, one that is not\nconnected, a caption over its limit. Every reason is returned at once, so\na composer can show them all rather than making someone fix them one by\none.","operationId":"create_post_api_admin_social_posts_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/posts/{post_id}":{"get":{"tags":["social"],"summary":"Get Post","description":"One post, as the desk lists it.\n\nWhat the admin reads while a released post goes out, from whichever page\nit is on: each delivery's state, so it can say when the post is done and\nhow every network took it.","operationId":"get_post_api_admin_social_posts__post_id__get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["social"],"summary":"Patch Post","description":"A post's own settings: on the bio page or off it, any time; its X post with\nthe link or without, and how TikTok takes it, while that network's delivery\nstill waits; how it is drawn, while nothing of it has gone out.\n\nNothing else: what was said and where it went are history, and the\nnetworks' copies would not follow an edit here anyway. A waiting X\ndelivery's caption is finished again for its new link setting, so the desk\nshows what X will get. A new template drops the renders already stored for\nthe post, so the next fetch draws it the new way.","operationId":"patch_post_api_admin_social_posts__post_id__patch","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/deliveries/{delivery_id}":{"patch":{"tags":["social"],"summary":"Patch Delivery","description":"Draw one network's copy of a post its own way: the picture, or one of the\nvideo templates, while that copy still waits. None goes back to the post's.\n\nHeld to what the worker will need, like the post's own template\n(`_redraw`): a renderer, a game, and a network and placement that take a\nclip this long — Threads takes none. Stored only where it differs from the\npost, so changing the post's template still moves every network that was\nnever set apart.","operationId":"patch_delivery_api_admin_social_deliveries__delivery_id__patch","parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"integer","title":"Delivery Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/videos":{"post":{"tags":["social"],"summary":"Upload Video","description":"A clip to post, as raw MP4 bytes, already made web-ready by the admin.\n\nThe admin encodes (H.264 High in yuv420p, AAC, the index at the front) and\nmeasures it with ffprobe, then sends the file and what it measured: this\nbox has no memory to spare for encoding, and no ffmpeg to measure with.\nWhat is checked here is what a broken hand-off would get wrong. The same\nfile twice is the same row: stored under the hash of its bytes.","operationId":"upload_video_api_admin_social_videos_post","parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":255,"title":"Name"}},{"name":"seconds","in":"query","required":true,"schema":{"type":"number","exclusiveMinimum":0,"title":"Seconds"}},{"name":"width","in":"query","required":true,"schema":{"type":"integer","minimum":1,"title":"Width"}},{"name":"height","in":"query","required":true,"schema":{"type":"integer","minimum":1,"title":"Height"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__social__VideoOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["social"],"summary":"List Videos","description":"The latest uploads, newest first: a clip can be posted again.","operationId":"list_videos_api_admin_social_videos_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":20,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/gcg__routers__social__VideoOut"},"title":"Response List Videos Api Admin Social Videos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/posts/{post_id}/card.jpg":{"get":{"tags":["social"],"summary":"Post Card","description":"The picture this post carries (feed, Facebook's square feed, or Story),\ndrawn if it has not been yet.","operationId":"post_card_api_admin_social_posts__post_id__card_jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"placement","in":"query","required":false,"schema":{"enum":["feed","square","story"],"type":"string","default":"feed","title":"Placement"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/posts/{post_id}/card.mp4":{"get":{"tags":["social"],"summary":"Post Clip","description":"The clip this post carries (feed, Facebook's square feed, or Story),\nrendered if it has not been yet. `template`: the clip a network set apart\non the desk carries instead (`clips.key`), for its preview.\n\nThe stored clip is served whether or not this server can still render:\n`clips.for_post` reads it first, and only a clip that has to be rendered\nnow is refused with the registry's reason (`clips._render`), the same one\nthe admin's picker shows.","operationId":"post_clip_api_admin_social_posts__post_id__card_mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"placement","in":"query","required":false,"schema":{"enum":["feed","square","story"],"type":"string","default":"feed","title":"Placement"}},{"name":"template","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Template"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/posts/{post_id}/release":{"post":{"tags":["social"],"summary":"Release","description":"Post it now: whatever of this post is still waiting falls due at once.\n\nReleased, not just re-dated. A delivery due now still waits out the gap\nafter its network's last post, goes behind anything older that is also\ndue, and — if the detector wrote it — waits for auto-posting to be turned\nback on. `released_at` is how the claim knows a person asked for this one\n(`gcg.social.queue.claim` says what that lets through, and what not).\n\nThe worker takes it on its next pass, `worker.PUBLISH_EVERY` apart. Only\nwhat is still scheduled moves: a send in flight cannot be hurried, and\nwhat went out, failed or was cancelled stays as it is.","operationId":"release_api_admin_social_posts__post_id__release_post","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/deliveries/{delivery_id}/cancel":{"post":{"tags":["social"],"summary":"Cancel","description":"Only while still waiting. A send in flight cannot be called back.","operationId":"cancel_api_admin_social_deliveries__delivery_id__cancel_post","parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"integer","title":"Delivery Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/deliveries/{delivery_id}/retry":{"post":{"tags":["social"],"summary":"Retry","description":"Send a failed delivery again, now.\n\nThis is the only way out of \"outcome unknown\", on purpose: the worker will\nnot retry a send it lost track of, because the network may already have\nposted it. A person checks the account, then presses this.","operationId":"retry_api_admin_social_deliveries__delivery_id__retry_post","parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"integer","title":"Delivery Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/detect":{"get":{"tags":["social"],"summary":"Preview Detection","description":"What a detection pass would queue right now. Writes nothing.","operationId":"preview_detection_api_admin_social_detect_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CandidateOut"},"title":"Response Preview Detection Api Admin Social Detect Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/preview":{"get":{"tags":["social"],"summary":"Preview","description":"A post exactly as the detector would write it for this game, per network,\nwith today's real numbers. Writes nothing.","operationId":"preview_api_admin_social_preview_get","parameters":[{"name":"game_id","in":"query","required":true,"schema":{"type":"integer","title":"Game Id"}},{"name":"kind","in":"query","required":false,"schema":{"enum":["price_drop","new_release","free_to_keep","top_reviewed","steam_hot","deal_of_day"],"type":"string","default":"price_drop","title":"Kind"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/preview/card.jpg":{"get":{"tags":["social"],"summary":"Preview Card","description":"The picture that preview would carry. Drawn every time, stored never.","operationId":"preview_card_api_admin_social_preview_card_jpg_get","parameters":[{"name":"game_id","in":"query","required":true,"schema":{"type":"integer","title":"Game Id"}},{"name":"kind","in":"query","required":false,"schema":{"enum":["price_drop","new_release","free_to_keep","top_reviewed","steam_hot","deal_of_day"],"type":"string","default":"price_drop","title":"Kind"}},{"name":"placement","in":"query","required":false,"schema":{"enum":["feed","square","story"],"type":"string","default":"feed","title":"Placement"}},{"name":"template","in":"query","required":false,"schema":{"type":"string","maxLength":24,"default":"minimal","title":"Template"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/preview/card.mp4":{"get":{"tags":["social"],"summary":"Preview Clip","description":"The clip that preview would carry with a video template.\n\nRendered on request and kept under the hash of its inputs (gcg.social.clips):\na clip takes seconds to make, and the picker asks for every template of\none game at once. The response itself is never cached — the price in it\nis today's.","operationId":"preview_clip_api_admin_social_preview_card_mp4_get","parameters":[{"name":"game_id","in":"query","required":true,"schema":{"type":"integer","title":"Game Id"}},{"name":"kind","in":"query","required":false,"schema":{"enum":["price_drop","new_release","free_to_keep","top_reviewed","steam_hot","deal_of_day"],"type":"string","default":"price_drop","title":"Kind"}},{"name":"placement","in":"query","required":false,"schema":{"enum":["feed","square","story"],"type":"string","default":"feed","title":"Placement"}},{"name":"template","in":"query","required":false,"schema":{"type":"string","maxLength":24,"default":"minimal","title":"Template"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/templates":{"get":{"tags":["social"],"summary":"List Templates","description":"Every template, cards first, with whether this server can render it and why not.","operationId":"list_templates_api_admin_social_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplatesOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/social/stats":{"get":{"tags":["social"],"summary":"Stats","description":"Whether this is working: reach on the networks, people following our links\nhere, and people going on to a store. See `gcg.social.stats.summary`.","operationId":"stats_api_admin_social_stats_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__social__StatsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/oauth/{network}/callback":{"get":{"tags":["social"],"summary":"Callback","operationId":"callback_api_social_oauth__network__callback_get","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/oauth/{network}/deauthorize":{"post":{"tags":["social"],"summary":"Deauthorize","operationId":"deauthorize_api_social_oauth__network__deauthorize_post","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/oauth/{network}/delete":{"post":{"tags":["social"],"summary":"Delete","operationId":"delete_api_social_oauth__network__delete_post","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/oauth/{network}/deletion":{"get":{"tags":["social"],"summary":"Deletion Status","description":"Where Meta points someone who asked for deletion. It happens on arrival.","operationId":"deletion_status_api_social_oauth__network__deletion_get","parameters":[{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/r/bio":{"get":{"tags":["social"],"summary":"Follow Bio","description":"The bio page's own \"browse every deal\" link: counted, then the site.","operationId":"follow_bio_api_social_r_bio_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"From"}},{"name":"ref","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":253},{"type":"null"}],"title":"Ref"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/r/{post_id}/{network}":{"get":{"tags":["social"],"summary":"Follow","description":"A link in one of our posts, or on its bio tile: on to its game, marked.\n\nA post that no longer exists, or has no game, still lands somewhere: a link\nalready out on a network is followed for as long as the post is up.\n\nA bio tile is counted here, credited to wherever the bio page's visitor\ncame from, which the page passes along. A caption link is NOT counted\nhere (see the module docstring): it is marked with its network and post,\nand counted when the page it lands on is shown (`arrive`).","operationId":"follow_api_social_r__post_id___network__get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"network","in":"path","required":true,"schema":{"type":"string","title":"Network"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"From"}},{"name":"ref","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":253},{"type":"null"}],"title":"Ref"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/social/arrive":{"post":{"tags":["social"],"summary":"Arrive","description":"A person arrived on the site from a link in one of our posts.\n\nThe click a caption link used to count at the redirect, counted where only\na person can make it: the site calls this once the page it opened is\nvisible in a browser (not prerendered, not a background tab), once per\npost per browser session. Meta's link fetchers follow the redirect but do\nnot run the page, so they never get here. Written as `kind='click'`,\n`via='arrival'`, which the dashboard counts (`stats.SOCIAL_CLICK`).\n\nThe body is parsed by hand rather than as a typed parameter, so it is read\nwhatever content type it arrives with: `navigator.sendBeacon` with a plain\nstring sends `text/plain`, and a beacon that is dropped is a lost click.\n\nRefused with 403 when the browser says the call came from another site\n(`Sec-Fetch-Site`), so another page cannot post clicks on our behalf\nthrough its visitors. Older browsers send no such header and are let\nthrough: the count is anonymous and the post must exist, so the most a\nforger gets is a number on our own dashboard.","operationId":"arrive_api_social_arrive_post","responses":{"204":{"description":"Successful Response"}}}},"/api/social/seen/bio":{"post":{"tags":["social"],"summary":"Bio Seen","description":"The link-in-bio page was opened.\n\nReported by the page itself, because the page is cached at the edge and\nmost opens never reach this server.","operationId":"bio_seen_api_social_seen_bio_post","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"From"}},{"name":"ref","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":253},{"type":"null"}],"title":"Ref"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/jobs":{"get":{"tags":["jobs"],"summary":"List Jobs","description":"Every job: schedule, next run, what is running now, how the last run went.","operationId":"list_jobs_api_admin_jobs_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/jobs/server":{"get":{"tags":["jobs"],"summary":"Server","description":"The host's CPU and memory, sampled every few seconds, and its free disk.","operationId":"server_api_admin_jobs_server_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/jobs/{name}/log":{"get":{"tags":["jobs"],"summary":"Job Log","description":"The end of one job's log, credentials taken out.","operationId":"job_log_api_admin_jobs__name__log_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,39}$","title":"Name"}},{"name":"lines","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Lines"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/jobs/{name}/start":{"post":{"tags":["jobs"],"summary":"Start Job","operationId":"start_job_api_admin_jobs__name__start_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,39}$","title":"Name"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/jobs/{name}/stop":{"post":{"tags":["jobs"],"summary":"Stop Job","operationId":"stop_job_api_admin_jobs__name__stop_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,39}$","title":"Name"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news":{"get":{"tags":["news"],"summary":"List Posts","description":"Every post, drafts and scheduled ones too, the most recently changed first.","operationId":"list_posts_api_admin_news_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["news"],"summary":"Create Post","description":"A new post. `slug` null makes one from the title; `publishedAt` null keeps it a draft.","operationId":"create_post_api_admin_news_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news/preview":{"post":{"tags":["news"],"summary":"Preview","description":"What a body WOULD be, saved: the same parser and checks, nothing stored.","operationId":"preview_api_admin_news_preview_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsPreviewIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsPreviewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news/{post_id}":{"get":{"tags":["news"],"summary":"Get Post","operationId":"get_post_api_admin_news__post_id__get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["news"],"summary":"Update Post","description":"The post replaced by what is sent, every field. `slug` null keeps a live\npost's address and makes a draft's from its title again. A change to the\ntitle, summary or body makes every translation of it outdated.","operationId":"update_post_api_admin_news__post_id__put","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["news"],"summary":"Delete Post","description":"The post, its rail, its translations and every cover it stored. Cannot be undone.","operationId":"delete_post_api_admin_news__post_id__delete","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news/{post_id}/translations":{"get":{"tags":["news"],"summary":"List Translations","description":"The English a translation is made from, with its hash; every translation\nof the post, current or outdated; and the languages it has none in. In\n`news.TRANSLATION_LOCALES` order.","operationId":"list_translations_api_admin_news__post_id__translations_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsTranslationsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news/{post_id}/translations/{locale}":{"put":{"tags":["news"],"summary":"Save Translation","description":"The post's translation into `locale`, made or replaced: `title`, `dek`\nand `bodyMd`, refused for what the post's own save refuses (the same\nlimits, the same parser, a `{{price:x}}` or `{{game:x}}` that names no\ngame). Stamped with the English as it is now (its hash, and the words\nthemselves as `sourceAtSave`), so a save makes it current.\n\n`sourceHash`, optional: the English's `source.hash` the translator read.\nWhen the English has changed since, the save is a 409 rather than a\ntranslation of text the post no longer has marked current.","operationId":"save_translation_api_admin_news__post_id__translations__locale__put","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"locale","in":"path","required":true,"schema":{"type":"string","title":"Locale"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsTranslationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["news"],"summary":"Delete Translation","description":"The post's translation into `locale`: readers in that language are served\nthe English. A 204 whether or not there was one, as the post is then\nwithout it either way; a 404 for a post that is not there.","operationId":"delete_translation_api_admin_news__post_id__translations__locale__delete","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"locale","in":"path","required":true,"schema":{"type":"string","title":"Locale"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/news/{post_id}/cover":{"post":{"tags":["news"],"summary":"Upload Cover","description":"A picture for the post: JPEG, PNG, WebP or AVIF, up to 10 MB, at least\n320x180. Known by its bytes, not by what it was sent as; cut to 16:9 and\nstripped of its metadata before anything is stored. It replaces the\nvideo's still or the borrowed game cover until it is removed.\n\nSent as a form's `file` field or as the raw picture (see \"the upload\"\nabove). A body declared or found to be past the cap is a 413 before any\nmore of it is read; a form that cannot be read is a 400, one without the\nfield a 422.","operationId":"upload_cover_api_admin_news__post_id__cover_post","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["news"],"summary":"Remove Cover","description":"The upload removed: the post falls back to its video's still (fetched now)\nor its first game's cover. Without an upload this only retries the still.","operationId":"remove_cover_api_admin_news__post_id__cover_delete","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNewsPostOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/mailing/lists":{"get":{"tags":["mailing"],"summary":"Lists","description":"This site's email lists, in `alerts.LISTS`' order: each `id` is a\n`?list=` the other routes take, its `name` how the admin shows it.","operationId":"lists_api_admin_mailing_lists_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ListOut"},"title":"Response Lists Api Admin Mailing Lists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/mailing/summary":{"get":{"tags":["mailing"],"summary":"Summary","description":"How big the list is, how it moved in the last thirty days, where it signed\nup and what it was sent; and the free games, whichever list is asked for\n(they are the site's, not a list's).","operationId":"summary_api_admin_mailing_summary_get","parameters":[{"name":"list","in":"query","required":false,"schema":{"enum":["giveaways","sales","news"],"type":"string","default":"giveaways","title":"List"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/mailing/subscribers":{"get":{"tags":["mailing"],"summary":"Subscribers","description":"One page of the list, newest signup first unless sorted otherwise, each\nrow with that list's emails to it (`alertsSent`, `lastAlertAt`).\n\n`q` is a case-insensitive piece of the address, matched literally: `_`\nand `%` are characters here, not wildcards. `counts` is per status for\nthe same search, for the filter chips.","operationId":"subscribers_api_admin_mailing_subscribers_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200,"default":"","title":"Q"}},{"name":"status","in":"query","required":false,"schema":{"enum":["all","subscribed","pending","unsubscribed"],"type":"string","default":"all","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","maxLength":32,"default":"","title":"Sort"}},{"name":"dir","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Dir"}},{"name":"list","in":"query","required":false,"schema":{"enum":["giveaways","sales","news"],"type":"string","default":"giveaways","title":"List"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribersOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/mailing/giveaways":{"get":{"tags":["mailing"],"summary":"Giveaways","description":"One page of the free games, newest giveaway first unless sorted otherwise:\none row per game per store, so a game free on Steam and on the Epic Games\nStore at once is two rows.\n\n`q` is a case-insensitive piece of the title (the store's, else the\ngame's), matched literally as on the subscribers. `counts` is per status\nand per store for the same search; see `GiveawayCounts`.","operationId":"giveaways_api_admin_mailing_giveaways_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200,"default":"","title":"Q"}},{"name":"status","in":"query","required":false,"schema":{"enum":["open","ended","all"],"type":"string","default":"all","title":"Status"}},{"name":"platform","in":"query","required":false,"schema":{"enum":["all","steam","epic","gog"],"type":"string","default":"all","title":"Platform"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","maxLength":32,"default":"","title":"Sort"}},{"name":"dir","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Dir"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__mailing__GiveawaysOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/mailing/export.csv":{"get":{"tags":["mailing"],"summary":"Export Csv","description":"The list as a spreadsheet: subscribed addresses unless asked otherwise.\nThe same columns for either list; `alerts_sent` is the row's\n`alertsSent`, so on the weekly deals the digests it was sent.\n\nUTF-8, RFC 4180 (CRLF, fields quoted where they need it), formula-safe.\nStreamed from a server-side cursor, so a large list is never held in\nmemory whole. The query runs before the answer starts, so a failure is\nstill a plain 500 rather than a cut-off file.","operationId":"export_csv_api_admin_mailing_export_csv_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"enum":["all","subscribed","pending","unsubscribed"],"type":"string","default":"subscribed","title":"Status"}},{"name":"list","in":"query","required":false,"schema":{"enum":["giveaways","sales","news"],"type":"string","default":"giveaways","title":"List"}},{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam":{"get":{"tags":["steam"],"summary":"State","description":"The signed-in account, or none.","operationId":"state_api_admin_steam_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__StateOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["steam"],"summary":"Sign Out","description":"Forget the account, and have Steam revoke its sign-in.","operationId":"sign_out_api_admin_steam_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignOutOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam/sign-in":{"post":{"tags":["steam"],"summary":"Sign In","description":"Start a sign-in: `{\"username\": ..., \"password\": ...}`. Read by hand; see the module.","operationId":"sign_in_api_admin_steam_sign_in_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__SignInOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam/sign-in/code":{"post":{"tags":["steam"],"summary":"Sign In Code","description":"The Steam Guard code the sign-in waits on.","operationId":"sign_in_code_api_admin_steam_sign_in_code_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__SignInOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam/sign-in/wait":{"post":{"tags":["steam"],"summary":"Sign In Wait","description":"Wait a while for an OK in the Steam app. Call again while it answers `guard`.","operationId":"sign_in_wait_api_admin_steam_sign_in_wait_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__SignInOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam/qr":{"post":{"tags":["steam"],"summary":"Sign In Qr","description":"Start a sign-in by QR code, to scan with the Steam app.","operationId":"sign_in_qr_api_admin_steam_qr_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__SignInOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/steam/qr/wait":{"post":{"tags":["steam"],"summary":"Sign In Qr Wait","description":"Wait a while for the Steam app to scan and approve. Call again while it answers `qr`.","operationId":"sign_in_qr_wait_api_admin_steam_qr_wait_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/gcg__routers__steam_account__SignInOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bio":{"get":{"tags":["bio"],"summary":"Page","operationId":"page_bio_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/bio/day/{day}":{"get":{"tags":["bio"],"summary":"Day Fragment","description":"One day's tiles, as the markup the page drops into that day's grid.\n\nOnly for days the page could list. Each URL is a separate entry in the\nedge cache, and an open date range would let anything that can loop over\ndates fill it with empty pages.","operationId":"day_fragment_bio_day__day__get","parameters":[{"name":"day","in":"path","required":true,"schema":{"type":"string","format":"date","title":"Day"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/products/covers/{game_id}/{width}.{fmt}":{"get":{"tags":["media"],"summary":"Cover","description":"Cover art, converted on first request and written to object storage.\n\nKeyed on the game ID. It used to be the slug, which read better in a URL\nand was the wrong key: a slug is rewritten underneath the file — retitling\na product changes it, and the reclaim pass in `rebuild-publishers` hands\nthem around — so the art orphaned with nothing to say why.\n\nIn production Caddy proxies `/media/*` to the bucket and only falls through\nhere when the object is missing, so this runs once per image and never\nagain. The bytes are returned as well as stored, so the visitor who\ntriggered the conversion gets the picture rather than a second round trip.","operationId":"cover_media_products_covers__game_id___width___fmt__get","parameters":[{"name":"game_id","in":"path","required":true,"schema":{"type":"integer","title":"Game Id"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Cover","description":"Cover art, converted on first request and written to object storage.\n\nKeyed on the game ID. It used to be the slug, which read better in a URL\nand was the wrong key: a slug is rewritten underneath the file — retitling\na product changes it, and the reclaim pass in `rebuild-publishers` hands\nthem around — so the art orphaned with nothing to say why.\n\nIn production Caddy proxies `/media/*` to the bucket and only falls through\nhere when the object is missing, so this runs once per image and never\nagain. The bytes are returned as well as stored, so the visitor who\ntriggered the conversion gets the picture rather than a second round trip.","operationId":"cover_media_products_covers__game_id___width___fmt__get","parameters":[{"name":"game_id","in":"path","required":true,"schema":{"type":"integer","title":"Game Id"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/news/covers/{post_id}/{version}/{width}.{fmt}":{"get":{"tags":["media"],"summary":"News Cover","description":"A news post's cover at one width, converted from its stored source on the\nfirst request and written once (gcg/services/news_covers.py).\n\nThe version is in the path and goes up with every picture a post stores,\nso the bytes behind one URL never change and are cached for a year as\n`immutable` — unlike a game cover above, whose key is the game alone. No\ndatabase read: the source at that version is the whole answer, and a\nversion a post has moved past is pruned, so it 404s here like any other.","operationId":"news_cover_media_news_covers__post_id___version___width___fmt__get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","title":"Version"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"News Cover","description":"A news post's cover at one width, converted from its stored source on the\nfirst request and written once (gcg/services/news_covers.py).\n\nThe version is in the path and goes up with every picture a post stores,\nso the bytes behind one URL never change and are cached for a year as\n`immutable` — unlike a game cover above, whose key is the game alone. No\ndatabase read: the source at that version is the whole answer, and a\nversion a post has moved past is pruned, so it 404s here like any other.","operationId":"news_cover_media_news_covers__post_id___version___width___fmt__get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","title":"Version"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/products/covers/franchises/{slug}/{version}/{width}.{fmt}":{"get":{"tags":["media"],"summary":"Franchise Cover","description":"A series' timeline cover at one width, drawn from its games on the first\nrequest for its current version and written once\n(gcg/services/franchise_covers.py). The version is a digest of what it is\ndrawn from, so the bytes behind one URL never change and are cached for a\nyear as `immutable`, as a news cover's are. Only the current version is\never drawn: anything else is a 404 here, not a render.","operationId":"franchise_cover_media_products_covers_franchises__slug___version___width___fmt__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","title":"Version"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Franchise Cover","description":"A series' timeline cover at one width, drawn from its games on the first\nrequest for its current version and written once\n(gcg/services/franchise_covers.py). The version is a digest of what it is\ndrawn from, so the bytes behind one URL never change and are cached for a\nyear as `immutable`, as a news cover's are. Only the current version is\never drawn: anything else is a 404 here, not a render.","operationId":"franchise_cover_media_products_covers_franchises__slug___version___width___fmt__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","title":"Version"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/{post_id}.jpg":{"get":{"tags":["media"],"summary":"Social Card","description":"The picture a social post carries — what Instagram and TikTok fetch.\n\nThe worker draws and stores it before it publishes, so this only runs on a\nmiss: a card that was never drawn (a post that went only to networks that\nunfurl links instead), or an object someone deleted. Same arrangement as\nthe covers above: Caddy asks the bucket first and falls through to here.","operationId":"social_card_media_social_cards__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Card","description":"The picture a social post carries — what Instagram and TikTok fetch.\n\nThe worker draws and stores it before it publishes, so this only runs on a\nmiss: a card that was never drawn (a post that went only to networks that\nunfurl links instead), or an object someone deleted. Same arrangement as\nthe covers above: Caddy asks the bucket first and falls through to here.","operationId":"social_card_media_social_cards__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/stories/{post_id}.jpg":{"get":{"tags":["media"],"summary":"Social Story Card","description":"The same post's full-screen 9:16 card, for Instagram and Facebook Stories.","operationId":"social_story_card_media_social_cards_stories__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Story Card","description":"The same post's full-screen 9:16 card, for Instagram and Facebook Stories.","operationId":"social_story_card_media_social_cards_stories__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/square/{post_id}.jpg":{"get":{"tags":["media"],"summary":"Social Square Card","description":"The same post's 1:1 card, for Facebook's feed (`cards.frame_for`).","operationId":"social_square_card_media_social_cards_square__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Square Card","description":"The same post's 1:1 card, for Facebook's feed (`cards.frame_for`).","operationId":"social_square_card_media_social_cards_square__post_id__jpg_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/videos/{name}.mp4":{"get":{"tags":["media"],"summary":"Social Video","description":"An uploaded post video, when the bucket did not answer first.\n\nIn production Caddy serves these straight from the bucket and this never\nruns; it is what serves them in development, where uploads land on disk.","operationId":"social_video_media_social_cards_videos__name__mp4_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Video","description":"An uploaded post video, when the bucket did not answer first.\n\nIn production Caddy serves these straight from the bucket and this never\nruns; it is what serves them in development, where uploads land on disk.","operationId":"social_video_media_social_cards_videos__name__mp4_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/{post_id}-{template}.mp4":{"get":{"tags":["media"],"summary":"Social Clip Variant","description":"A clip one network draws another way than its post (`clips.key`).","operationId":"social_clip_variant_media_social_cards_motion__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Clip Variant","description":"A clip one network draws another way than its post (`clips.key`).","operationId":"social_clip_variant_media_social_cards_motion__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/stories/{post_id}-{template}.mp4":{"get":{"tags":["media"],"summary":"Social Story Clip Variant","operationId":"social_story_clip_variant_media_social_cards_motion_stories__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Story Clip Variant","operationId":"social_story_clip_variant_media_social_cards_motion_stories__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/square/{post_id}-{template}.mp4":{"get":{"tags":["media"],"summary":"Social Square Clip Variant","operationId":"social_square_clip_variant_media_social_cards_motion_square__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Square Clip Variant","operationId":"social_square_clip_variant_media_social_cards_motion_square__post_id___template__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}},{"name":"template","in":"path","required":true,"schema":{"type":"string","title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/{post_id}.mp4":{"get":{"tags":["media"],"summary":"Social Clip","description":"The clip a post rendered from a video template carries — what Instagram,\nFacebook and TikTok fetch. Drawn and stored by the worker before it\npublishes, like the card above, so this runs on a miss or in development.","operationId":"social_clip_media_social_cards_motion__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Clip","description":"The clip a post rendered from a video template carries — what Instagram,\nFacebook and TikTok fetch. Drawn and stored by the worker before it\npublishes, like the card above, so this runs on a miss or in development.","operationId":"social_clip_media_social_cards_motion__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/stories/{post_id}.mp4":{"get":{"tags":["media"],"summary":"Social Story Clip","description":"The same post's 9:16 clip, for Instagram and Facebook Stories.","operationId":"social_story_clip_media_social_cards_motion_stories__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Story Clip","description":"The same post's 9:16 clip, for Instagram and Facebook Stories.","operationId":"social_story_clip_media_social_cards_motion_stories__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/motion/square/{post_id}.mp4":{"get":{"tags":["media"],"summary":"Social Square Clip","description":"The same post's 1:1 clip, for Facebook's feed (`cards.frame_for`).","operationId":"social_square_clip_media_social_cards_motion_square__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Square Clip","description":"The same post's 1:1 clip, for Facebook's feed (`cards.frame_for`).","operationId":"social_square_clip_media_social_cards_motion_square__post_id__mp4_get","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/cards/previews/{name}.mp4":{"get":{"tags":["media"],"summary":"Social Preview Clip","description":"A clip the admin previewed, when the bucket did not answer first.\n\nRendered on request by the social router and kept under a hash of its\ninputs; in production Caddy serves it from the bucket and this never runs.","operationId":"social_preview_clip_media_social_cards_previews__name__mp4_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Preview Clip","description":"A clip the admin previewed, when the bucket did not answer first.\n\nRendered on request by the social router and kept under a hash of its\ninputs; in production Caddy serves it from the bucket and this never runs.","operationId":"social_preview_clip_media_social_cards_previews__name__mp4_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/social/{name}.txt":{"get":{"tags":["media"],"summary":"Social Verification","description":"TikTok's proof that this site owns /media/social/, where the cards live.\n\nTikTok pulls a photo only from a URL prefix verified for the app, and it\nverifies one by fetching the signature file it handed out from that\nprefix. The file's name and contents come from the environment, so\nverifying a new app is a config change rather than a file on the box.","operationId":"social_verification_media_social__name__txt_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Social Verification","description":"TikTok's proof that this site owns /media/social/, where the cards live.\n\nTikTok pulls a photo only from a URL prefix verified for the app, and it\nverifies one by fetching the signature file it handed out from that\nprefix. The file's name and contents come from the environment, so\nverifying a new app is a config change rather than a file on the box.","operationId":"social_verification_media_social__name__txt_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/covers/{slug}/{width}.{fmt}":{"get":{"tags":["media"],"summary":"Legacy Cover","description":"The slug-keyed URL this site served until the artwork moved to the bucket.\n\nKept because these are not only in old bookmarks. `og:image` is an ABSOLUTE\nurl — `https://getcheapgames.com/media/covers/<slug>/616.jpg` — so every\nlink ever shared to Facebook, X, Discord, WhatsApp or LinkedIn carries this\nshape, and their scrapers re-fetch it. Dropping it would break the preview\nimage on shares that already exist, which is the one part of this migration\na visitor would actually see.\n\n302, not 301. A slug is not permanently bound to a product: retitling frees\nthe old one and the reclaim pass can hand it to a different row, so a\npermanent redirect cached in a browser could one day point at the wrong\ngame's art. A day of caching keeps the cost to one extra hop per client per\nday, and leaves us able to change our minds.","operationId":"legacy_cover_media_covers__slug___width___fmt__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"tags":["media"],"summary":"Legacy Cover","description":"The slug-keyed URL this site served until the artwork moved to the bucket.\n\nKept because these are not only in old bookmarks. `og:image` is an ABSOLUTE\nurl — `https://getcheapgames.com/media/covers/<slug>/616.jpg` — so every\nlink ever shared to Facebook, X, Discord, WhatsApp or LinkedIn carries this\nshape, and their scrapers re-fetch it. Dropping it would break the preview\nimage on shares that already exist, which is the one part of this migration\na visitor would actually see.\n\n302, not 301. A slug is not permanently bound to a product: retitling frees\nthe old one and the reclaim pass can hand it to a different row, so a\npermanent redirect cached in a browser could one day point at the wrong\ngame's art. A day of caching keeps the cost to one extra hop per client per\nday, and leaves us able to change our minds.","operationId":"legacy_cover_media_covers__slug___width___fmt__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"width","in":"path","required":true,"schema":{"type":"integer","title":"Width"}},{"name":"fmt","in":"path","required":true,"schema":{"type":"string","title":"Fmt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mcp":{"get":{"tags":["mcp"],"summary":"Mcp Get","description":"No server-sent stream: the transport's own way of saying so is 405.","operationId":"mcp_get_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["mcp"],"summary":"Mcp Post","description":"The Streamable HTTP endpoint: one message or a batch in, JSON out.","operationId":"mcp_post_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["mcp"],"summary":"Mcp Delete","description":"There is no session to end.","operationId":"mcp_delete_mcp_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp/chatgpt":{"get":{"tags":["mcp"],"summary":"Mcp Get","description":"No server-sent stream: the transport's own way of saying so is 405.","operationId":"mcp_get_mcp_chatgpt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["mcp"],"summary":"Mcp Chatgpt Post","description":"The ChatGPT edition: the same, every game linked to its page alone.","operationId":"mcp_chatgpt_post_mcp_chatgpt_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["mcp"],"summary":"Mcp Delete","description":"There is no session to end.","operationId":"mcp_delete_mcp_chatgpt_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/.well-known/openai-apps-challenge":{"get":{"tags":["mcp"],"summary":"Openai Apps Challenge","description":"OpenAI's domain check for the plugin directory: the token its portal\nshows, as plain text and nothing else, on the MCP host\n(`GCG_OPENAI_APPS_CHALLENGE`). Not found until it is set.","operationId":"openai_apps_challenge__well_known_openai_apps_challenge_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/track/free-claim":{"post":{"tags":["track"],"summary":"Free Claim","description":"Count one press of a free \"Claim\" or \"Play now\" button, on a card or the game page.\n\nThe body is parsed by hand rather than as a typed parameter, so it is read\nwhatever content type it comes with: a keepalive `fetch` sends JSON, and\n`navigator.sendBeacon` with a string sends `text/plain`.\n\nNot counted when the browser says the call came from another site\n(`Sec-Fetch-Site`), so another page cannot press our buttons through its\nvisitors. Older browsers send no such header and are let through: the\ncount is anonymous and the game must exist, so the most a forger gets is\na number on our own dashboard.","operationId":"free_claim_api_track_free_claim_post","responses":{"204":{"description":"Successful Response"}}}},"/api/track/visit":{"post":{"tags":["track"],"summary":"Visit","description":"Count one landing from a tagged link (`utm_source`), cookieless and with no\nconsent needed: it keeps the campaign's tags, the path, a country and a\nclass of browser, nothing that identifies the visitor (gcg/services/visits.py).\n\nA robot that runs the page is recorded too, as `bot`, so the readout can\ntell paid clicks that were people from those that were not.","operationId":"visit_api_track_visit_post","responses":{"204":{"description":"Successful Response"}}}},"/api/trending":{"get":{"tags":["meta"],"summary":"Trending","description":"The \"Trending now\" links the home page and the deals page show: the first\n`TRENDING_LINKS` priority games (gcg/services/priority.py), slug and title.\nThe same list the crawler shell links, from the same memo.","operationId":"trending_api_trending_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Response Trending Api Trending Get"}}}}}}},"/sitemap-priority.xml":{"get":{"tags":["meta"],"summary":"Sitemap Priority","description":"The games we most want crawled first, in the markets search comes from: at\nmost 1,000 URLs, listed first in the index and in robots.txt.\n\nThe product files list ~4M URLs between them, and Google crawls a slice:\non 15 Oct 2026 the GTA VI page (\"gta 6 price\", 60.5k searches a month) was\n\"unknown to Google\", one entry among 25,060 in its tier's file. This file\nis short enough to be read whole on every visit. Which games, and in what\norder, is gcg/services/priority.py; each is dated by when its prices last\nmoved, the date the product files give it too.","operationId":"sitemap_priority_sitemap_priority_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sitemap.xml":{"get":{"tags":["meta"],"summary":"Sitemap Index","description":"An INDEX, not a sitemap.\n\nThe catalogue is ~35,000 products served to 40 markets, and every market is\na real URL with its own price and its own language. As one file that is\n1.4 million entries — Google caps a sitemap at 50,000 URLs and 50 MB — so\nthis lists one entry per market per chunk instead.\n\nA sitemap index may NOT point at another index, so every chunk of every\nmarket is listed here directly: the hubs (`sitemap-ie-1.xml`, ...), then\neach offered tier of products in its own series (`sitemap-ie-t1-1.xml`,\n...; see gcg/services/sitemap_tiers.py), so Search Console reports how\nmuch of each tier is indexed. Tier 1 alone is about 20,000 products, one\npart per market, so 40 markets are 80 entries, nowhere near the 50,000 an\nindex may hold.\n\nEach product part is dated by the newest `lastmod` it lists, read from the\nrows it is written from (`_part_lastmod`); a product's date is the same in\nevery market, so one reading dates all forty copies. A hub part has no\ndate to give: its entries carry none (see `sitemap_chunk`).\n\nCounted from the same lists the parts are cut from (`_SITEMAP_MEMO`), so\nwithin an hour the index and the files it names agree exactly.","operationId":"sitemap_index_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sitemap-{code}-t{tier}-{part}.xml":{"get":{"tags":["meta"],"summary":"Sitemap Tier Chunk","description":"One slice of one tier of one market's products (gcg/services/sitemap_tiers.py).\n\nDeclared before `sitemap_chunk`, whose `{code}` would otherwise take\n\"ie-t1\" for a market and answer 404. A tier not offered now has no file:\nthe index does not name it, and the pages in it are not offered.\n\nCut from the list built once for every market and part (`_products`), so\na file is written from memory: the query behind it is the same for all\nforty markets, and it is only the prefix that differs.","operationId":"sitemap_tier_chunk_sitemap__code__t_tier___part__xml_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"tier","in":"path","required":true,"schema":{"type":"integer","title":"Tier"}},{"name":"part","in":"path","required":true,"schema":{"type":"integer","title":"Part"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sitemap-{code}-{part}.xml":{"get":{"tags":["meta"],"summary":"Sitemap Chunk","description":"One slice of one market's hub pages: the static routes, every news post,\npublisher and series. `SITEMAP_CHUNK` at a time; the hubs were not chunked\nat all once, and part 1 reached 56.2 MB, past Google's 50 MB cap, so it\nwas rejected for every market.\n\nThe products were the parts after these until 12 October 2026, and are\neach tier's own series now (`sitemap_tier_chunk`): a number past the hubs\nnames no file.","operationId":"sitemap_chunk_sitemap__code___part__xml_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"part","in":"path","required":true,"schema":{"type":"integer","title":"Part"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sitemap-{code}.xml":{"get":{"tags":["meta"],"summary":"Sitemap For Country","description":"The pre-chunk URL, which Search Console may still have on file.\n\nA 404 here would be reported as a sitemap that stopped working. It is not\nequivalent to any one chunk, so it points at the index, which lists every\nchunk of every market.","operationId":"sitemap_for_country_sitemap__code__xml_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms.txt":{"get":{"tags":["meta"],"summary":"Llms Txt","description":"https://llmstxt.org — a curated map of the site for language models.\n\nCarries no prices, deliberately. On a price comparison site a stale number\nquoted back by an answer engine is worse than no number, so this points at\nwhere to read the live figure instead.\n\nSmall on purpose: the guidance, the core pages and the most-reviewed games,\nthen `/llms-full.txt` for every game we track.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms-full.txt":{"get":{"tags":["meta"],"summary":"Llms Full Txt","description":"llms.txt with every game we track, for a reader that wants the lot.","operationId":"llms_full_txt_llms_full_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/robots.txt":{"get":{"tags":["meta"],"summary":"Robots Txt","description":"The `/*/search` wildcard is load-bearing.\n\nEvery real path is country-prefixed (`/gb/search?q=...`), and a `Disallow`\nis a PREFIX match from the start of the path — so the plain `/search` rule\nthis shipped with matched nothing at all, and internal search was fully\ncrawlable in all 39 markets. The page's own `noindex` only exists once a\ncrawler has executed the JavaScript, by which point the crawl has been paid\nfor. The unprefixed rule is kept below for the bare form.\n\nThe catalogue API is NOT disallowed, and must never be again. Google and\nBing render the app, and a renderer honours robots.txt for every fetch the\npage makes: with `Disallow: /api/` every call the app renders from was\nrefused, so the page they indexed had no title, no description, no\ncanonical, no hreflang, no JSON-LD and no content — the shell's tags are\nremoved on boot (main.tsx) and nothing replaced them. Measured by loading\nthe shell with /api/ refused: an empty page, against 1,073 words and 47\nlinks with it allowed. The JSON is kept out of the index by\n`X-Robots-Tag: noindex` instead (main.py), which a renderer still fetches\npast. Only the endpoints that are not reads stay disallowed: the affiliate\nredirect, the social callbacks, and search.\n\nNo `Allow: /` line. Everything is allowed by default, and a parser that\ntakes the FIRST matching rule rather than the longest — Python's own\n`robotparser`, and crawlers built on it — read the `Allow: /` that used to\nhead this group as permission for every path, disallowed ones included.\nAnd no blank line inside the group: the original robots.txt convention\nends a record at one, so to those parsers every rule after it belonged to\nno user-agent at all.","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/_meta/{full_path}":{"get":{"tags":["meta"],"summary":"Crawler Shell","description":"The SPA shell with real metadata already in it, for clients that do not run\nJavaScript.\n\nEvery tag this site needs is generated correctly — title, description,\ncanonical, Open Graph, Twitter card, hreflang, Product JSON-LD — but React\nwrites them after hydration, so a crawler that does not execute scripts\nreceives 794 bytes of empty document. Google renders and eventually sees\nthem; Facebook, WhatsApp, LinkedIn, Discord, Slack and X never do, which is\nwhy every share of this site is a bare grey card.\n\nCaddy sends only known crawler user-agents here. Browsers keep the static\nfile, so this path cannot slow down or break a real visit — and a crawler\nthat slips through unmatched is no worse off than it is today.\n\nProduct pages get their own title, price and cover. Everything else gets\nthe site's own identity, which is still infinitely better than nothing.","operationId":"crawler_shell__meta__full_path__get","parameters":[{"name":"full_path","in":"path","required":true,"schema":{"type":"string","title":"Full Path"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"tags":["meta"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AccountPatch":{"properties":{"paused":{"type":"boolean","title":"Paused"}},"type":"object","required":["paused"],"title":"AccountPatch"},"AdminNewsListOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AdminNewsPostOut"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"AdminNewsListOut"},"AdminNewsPostOut":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"tag":{"type":"string","title":"Tag"},"author":{"type":"string","title":"Author"},"bodyMd":{"type":"string","title":"Bodymd"},"blocks":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Blocks"},"readingMinutes":{"type":"integer","title":"Readingminutes"},"videoId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Videoid"},"gameIds":{"items":{"type":"integer"},"type":"array","title":"Gameids"},"games":{"items":{"$ref":"#/components/schemas/NewsGameRefOut"},"type":"array","title":"Games"},"cover":{"anyOf":[{"$ref":"#/components/schemas/NewsCoverOut"},{"type":"null"}]},"coverSource":{"anyOf":[{"type":"string","enum":["upload","youtube","game"]},{"type":"null"}],"title":"Coversource"},"publishedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Publishedat"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"status":{"type":"string","enum":["draft","scheduled","published"],"title":"Status"},"url":{"type":"string","title":"Url"},"translations":{"additionalProperties":{"type":"string","enum":["current","outdated"]},"type":"object","title":"Translations","default":{}}},"type":"object","required":["id","slug","title","dek","tag","author","bodyMd","blocks","readingMinutes","videoId","gameIds","games","cover","coverSource","publishedAt","createdAt","updatedAt","status","url"],"title":"AdminNewsPostOut","description":"A post as the Midfeed admin edits it, drafts included."},"AgeRatingOut":{"properties":{"board":{"type":"string","enum":["pegi","esrb","usk","classind"],"title":"Board"},"rating":{"type":"string","title":"Rating"},"badge":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge"},"icons":{"items":{"type":"string"},"type":"array","title":"Icons","default":[]},"descriptors":{"items":{"type":"string"},"type":"array","title":"Descriptors","default":[]},"interactive":{"items":{"type":"string"},"type":"array","title":"Interactive","default":[]},"requiredAge":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requiredage"}},"type":"object","required":["board","rating"],"title":"AgeRatingOut","description":"The age rating official in the reader's country, from Steam, in our\nformat (steam_ratings.py): one board, or none at all."},"AlertOut":{"properties":{"status":{"type":"string","enum":["sent","queued","confirmed","unsubscribed","expired","invalid"],"title":"Status"},"list":{"anyOf":[{"type":"string","enum":["giveaways","sales"]},{"type":"null"}],"title":"List"}},"type":"object","required":["status"],"title":"AlertOut"},"AlertSubscribeIn":{"properties":{"email":{"type":"string","maxLength":320,"title":"Email"},"country":{"type":"string","maxLength":8,"title":"Country","default":""},"list":{"type":"string","enum":["giveaways","sales"],"title":"List","default":"giveaways"}},"type":"object","required":["email"],"title":"AlertSubscribeIn"},"BestPointOut":{"properties":{"day":{"type":"string","format":"date","title":"Day"},"lowCents":{"type":"integer","title":"Lowcents"},"highCents":{"type":"integer","title":"Highcents"},"closeCents":{"type":"integer","title":"Closecents"},"bestStore":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beststore"},"converted":{"type":"boolean","title":"Converted","default":false}},"type":"object","required":["day","lowCents","highCents","closeCents"],"title":"BestPointOut"},"Breakdown":{"properties":{"label":{"type":"string","title":"Label"},"rows":{"items":{"$ref":"#/components/schemas/BreakdownRow"},"type":"array","title":"Rows"}},"type":"object","required":["label","rows"],"title":"Breakdown"},"BreakdownRow":{"properties":{"key":{"type":"string","title":"Key"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["key","count"],"title":"BreakdownRow"},"CandidateOut":{"properties":{"kind":{"type":"string","title":"Kind"},"game_id":{"type":"integer","title":"Game Id"},"title":{"type":"string","title":"Title"},"currency":{"type":"string","title":"Currency"},"price_cents":{"type":"integer","title":"Price Cents"},"was_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Was Cents"},"off_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Off Pct"},"stores":{"type":"integer","title":"Stores"},"networks":{"items":{"type":"string"},"type":"array","title":"Networks"},"reason":{"type":"string","title":"Reason"},"store":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Store"},"official":{"type":"boolean","title":"Official","default":false},"steam_price_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steam Price Cents"},"reviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reviews"},"review_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Review Pct"},"rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rank"},"chart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chart"},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"}},"type":"object","required":["kind","game_id","title","currency","price_cents","was_cents","off_pct","stores","networks","reason"],"title":"CandidateOut"},"CaptionOut":{"properties":{"network":{"type":"string","title":"Network"},"name":{"type":"string","title":"Name"},"caption":{"type":"string","title":"Caption"},"picture":{"type":"boolean","title":"Picture"},"story":{"type":"boolean","title":"Story"}},"type":"object","required":["network","name","caption","picture","story"],"title":"CaptionOut"},"CodeIn":{"properties":{"attempt":{"type":"string","title":"Attempt"},"code":{"type":"string","title":"Code"}},"type":"object","required":["attempt","code"],"title":"CodeIn"},"ContentRef":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"kind":{"type":"string","title":"Kind"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"fromByPlatform":{"additionalProperties":{"type":"integer"},"type":"object","title":"Frombyplatform","default":{}},"fromCurrencyByPlatform":{"additionalProperties":{"type":"string"},"type":"object","title":"Fromcurrencybyplatform","default":{}}},"type":"object","required":["slug","title","kind"],"title":"ContentRef","description":"One thing a package grants — the base game, or a DLC."},"ConversionOut":{"properties":{"source":{"type":"string","title":"Source"},"base":{"type":"string","title":"Base"},"rate":{"type":"number","title":"Rate"},"rateDay":{"type":"string","format":"date","title":"Rateday"}},"type":"object","required":["source","base","rate","rateDay"],"title":"ConversionOut"},"CountryOut":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"currency":{"type":"string","title":"Currency"},"hasOfficialStore":{"type":"boolean","title":"Hasofficialstore"}},"type":"object","required":["code","name","currency","hasOfficialStore"],"title":"CountryOut"},"Counts":{"properties":{"subscribed":{"type":"integer","title":"Subscribed"},"pending":{"type":"integer","title":"Pending"},"unsubscribed":{"type":"integer","title":"Unsubscribed"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["subscribed","pending","unsubscribed","total"],"title":"Counts"},"DailyPointOut":{"properties":{"day":{"type":"string","format":"date","title":"Day"},"lowCents":{"type":"integer","title":"Lowcents"},"highCents":{"type":"integer","title":"Highcents"},"closeCents":{"type":"integer","title":"Closecents"}},"type":"object","required":["day","lowCents","highCents","closeCents"],"title":"DailyPointOut","description":"One observed day. Absent from the list entirely if we observed nothing."},"DeliveryOut":{"properties":{"id":{"type":"integer","title":"Id"},"network":{"type":"string","title":"Network"},"placement":{"type":"string","title":"Placement"},"state":{"type":"string","title":"State"},"caption":{"type":"string","title":"Caption"},"due_at":{"type":"string","format":"date-time","title":"Due At"},"attempts":{"type":"integer","title":"Attempts"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"likes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likes"},"comments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comments"},"shares":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shares"},"stats_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Stats At"},"released_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Released At"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"},"planned_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Planned At"},"planned_why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Planned Why"}},"type":"object","required":["id","network","placement","state","caption","due_at","attempts","last_error","url","sent_at"],"title":"DeliveryOut"},"DeliveryPatch":{"properties":{"template":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Template"}},"type":"object","title":"DeliveryPatch","description":"One network's template: an id, or None for the post's own again."},"EditionRef":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"edition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edition"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"fromCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fromcents"},"fromCurrency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fromcurrency"},"fromByPlatform":{"additionalProperties":{"type":"integer"},"type":"object","title":"Frombyplatform","default":{}},"fromCurrencyByPlatform":{"additionalProperties":{"type":"string"},"type":"object","title":"Fromcurrencybyplatform","default":{}}},"type":"object","required":["slug","title"],"title":"EditionRef","description":"A sibling product: another edition of the same base game, or the base game\nitself. Enough to render a link and a box; not the whole product."},"FiltersOut":{"properties":{"stores":{"items":{"$ref":"#/components/schemas/StoreRefOut"},"type":"array","title":"Stores"},"platforms":{"items":{"type":"string"},"type":"array","title":"Platforms"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres","default":[]}},"type":"object","required":["stores","platforms","regions"],"title":"FiltersOut","description":"What the sidebar can offer, as the data currently stands.\n\nServed rather than hardcoded in the client: regions come and go with\nlistings, and a checkbox for a region nobody sells in is a dead end."},"ForeignTrackOut":{"properties":{"currency":{"type":"string","title":"Currency"},"reason":{"type":"string","title":"Reason"},"series":{"items":{"$ref":"#/components/schemas/HistorySeriesOut"},"type":"array","title":"Series"}},"type":"object","required":["currency","reason","series"],"title":"ForeignTrackOut","description":"Series we hold but cannot express in the visitor's currency.\n\nNever merged into the main panel: they get their own axis in their own\nmoney. This is the designed state for the six currencies the ECB does not\npublish — CLP, COP, CRC, GTQ, PEN, UYU — not an error path."},"FoundGameOut":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"releaseYear":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Releaseyear"},"releaseDate":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Releasedate"},"publisher":{"type":"string","title":"Publisher"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"steamAppId":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steamappid"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"priceHistory":{"items":{"type":"integer"},"type":"array","title":"Pricehistory","default":[]},"kind":{"type":"string","title":"Kind","default":"game"},"isFree":{"type":"boolean","title":"Isfree","default":false},"freeToKeep":{"type":"boolean","title":"Freetokeep","default":false},"giveaway":{"anyOf":[{"$ref":"#/components/schemas/gcg__schemas__GiveawayOut"},{"type":"null"}]},"playUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playurl"},"edition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edition"},"franchise":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Franchise"},"offers":{"items":{"$ref":"#/components/schemas/OfferCardOut"},"type":"array","title":"Offers","default":[]},"reviewPercentPositive":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reviewpercentpositive"},"reviewCount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reviewcount"}},"type":"object","required":["slug","title","releaseYear","publisher","genres"],"title":"FoundGameOut","description":"A card from `/api/find`, with Steam's review summary it was ranked by:\nthe share of positive reviews and how many there are."},"FranchiseChapterOut":{"properties":{"years":{"type":"string","title":"Years"},"title":{"type":"string","title":"Title"},"text":{"type":"string","title":"Text"}},"type":"object","required":["years","title","text"],"title":"FranchiseChapterOut","description":"One period of a series' story (`franchise_profiles.Chapter`)."},"FranchiseOut":{"properties":{"name":{"type":"string","title":"Name"},"games":{"type":"integer","title":"Games"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"publisher":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publisher"}},"type":"object","required":["name","games"],"title":"FranchiseOut","description":"A series, with how many priced games we carry for it.\n\nNo slug: a franchise has no row of its own, so the name is the identifier\nand the URL carries it."},"FranchiseStoryOut":{"properties":{"name":{"type":"string","title":"Name"},"since":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Since"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"chapters":{"items":{"$ref":"#/components/schemas/FranchiseChapterOut"},"type":"array","title":"Chapters"}},"type":"object","required":["name","since","coverBase","chapters"],"title":"FranchiseStoryOut","description":"A series' cover as its page shows it (gcg/services/franchise_covers.py),\nand for the few with a profile (gcg/data/franchise_profiles.json) the year\nit began and its story, English for every market, as a news post is: it is\nwritten once, by hand."},"GameCardOut":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"releaseYear":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Releaseyear"},"releaseDate":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Releasedate"},"publisher":{"type":"string","title":"Publisher"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"steamAppId":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steamappid"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"priceHistory":{"items":{"type":"integer"},"type":"array","title":"Pricehistory","default":[]},"kind":{"type":"string","title":"Kind","default":"game"},"isFree":{"type":"boolean","title":"Isfree","default":false},"freeToKeep":{"type":"boolean","title":"Freetokeep","default":false},"giveaway":{"anyOf":[{"$ref":"#/components/schemas/gcg__schemas__GiveawayOut"},{"type":"null"}]},"playUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playurl"},"edition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edition"},"franchise":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Franchise"},"offers":{"items":{"$ref":"#/components/schemas/OfferCardOut"},"type":"array","title":"Offers","default":[]}},"type":"object","required":["slug","title","releaseYear","publisher","genres"],"title":"GameCardOut","description":"A game as a listing renders it.\n\nDeclared rather than derived from `GameOut` so the wire shape of a listing\nis readable in one place. The fields a listing never fills — description,\nthe edition family, package contents — are simply absent instead of being\nserialised as a screenful of nulls."},"GameNewsOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/NewsCardOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["items","total"],"title":"GameNewsOut","description":"The posts that cover one product (`GameOut.news`, sent with its page by\n`GET /api/games/{slug}`): the newest few (`news.ABOUT_GAME`), as the\nindex's cards, and how many there are in all, so a page can say there\nare more than it shows."},"GameOut":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"releaseYear":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Releaseyear"},"releaseDate":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Releasedate"},"publisher":{"type":"string","title":"Publisher"},"developer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Developer"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"steamAppId":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steamappid"},"coverBase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverbase"},"priceHistory":{"items":{"type":"integer"},"type":"array","title":"Pricehistory","default":[]},"metacriticScore":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Metacriticscore"},"metacriticUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metacriticurl"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"descriptionLocale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Descriptionlocale"},"kind":{"type":"string","title":"Kind","default":"game"},"isFree":{"type":"boolean","title":"Isfree","default":false},"freeToKeep":{"type":"boolean","title":"Freetokeep","default":false},"giveaway":{"anyOf":[{"$ref":"#/components/schemas/gcg__schemas__GiveawayOut"},{"type":"null"}]},"playUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playurl"},"edition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edition"},"steamSubId":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steamsubid"},"franchise":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Franchise"},"publisherSlug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publisherslug"},"baseGame":{"anyOf":[{"$ref":"#/components/schemas/EditionRef"},{"type":"null"}]},"editions":{"items":{"$ref":"#/components/schemas/EditionRef"},"type":"array","title":"Editions","default":[]},"contents":{"items":{"$ref":"#/components/schemas/ContentRef"},"type":"array","title":"Contents","default":[]},"seasonPasses":{"items":{"$ref":"#/components/schemas/GameOut"},"type":"array","title":"Seasonpasses","default":[]},"dlc":{"items":{"$ref":"#/components/schemas/GameOut"},"type":"array","title":"Dlc","default":[]},"soundtracks":{"items":{"$ref":"#/components/schemas/GameOut"},"type":"array","title":"Soundtracks","default":[]},"requiresGame":{"anyOf":[{"$ref":"#/components/schemas/GameOut"},{"type":"null"}]},"includedIn":{"items":{"$ref":"#/components/schemas/GameOut"},"type":"array","title":"Includedin","default":[]},"video":{"anyOf":[{"$ref":"#/components/schemas/gcg__schemas__VideoOut"},{"type":"null"}]},"screenshots":{"items":{"$ref":"#/components/schemas/ScreenshotOut"},"type":"array","title":"Screenshots","default":[]},"pricesChangedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Priceschangedat"},"specs":{"anyOf":[{"$ref":"#/components/schemas/SpecsOut"},{"type":"null"}]},"ageRating":{"anyOf":[{"$ref":"#/components/schemas/AgeRatingOut"},{"type":"null"}]},"steamReviews":{"anyOf":[{"$ref":"#/components/schemas/SteamReviewsOut"},{"type":"null"}]},"gcgRating":{"anyOf":[{"$ref":"#/components/schemas/GcgRatingOut"},{"type":"null"}]},"news":{"anyOf":[{"$ref":"#/components/schemas/GameNewsOut"},{"type":"null"}]},"offers":{"items":{"$ref":"#/components/schemas/OfferOut"},"type":"array","title":"Offers","default":[]}},"type":"object","required":["slug","title","releaseYear","publisher","genres"],"title":"GameOut"},"GcgRatingOut":{"properties":{"average":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["average","count"],"title":"GcgRatingOut","description":"Our users' rating of the product (gcg/reviews): the average of its\nvisible reviews to one decimal, and how many. The page's header and its\nreviews tab read it here, without asking `/reviews` first."},"GeoOut":{"properties":{"country":{"type":"string","title":"Country"},"currency":{"type":"string","title":"Currency"},"detected":{"type":"boolean","title":"Detected"}},"type":"object","required":["country","currency","detected"],"title":"GeoOut"},"GiveawayCountOut":{"properties":{"count":{"type":"integer","title":"Count"}},"type":"object","required":["count"],"title":"GiveawayCountOut","description":"How many games are free to keep right now: the free-to-play button's badge."},"GiveawayCounts":{"properties":{"open":{"type":"integer","title":"Open"},"ended":{"type":"integer","title":"Ended"},"all":{"type":"integer","title":"All"},"byPlatform":{"$ref":"#/components/schemas/PlatformCounts"}},"type":"object","required":["open","ended","all","byPlatform"],"title":"GiveawayCounts","description":"What each chip would show, for the same search. Each row of chips is\ncounted under the OTHER row's choice: the status chips on the store\nchosen, the store chips in the status chosen; so the chip that is lit\nalways reads the table's `total`."},"GiveawaysSummary":{"properties":{"open":{"type":"integer","title":"Open"},"last30d":{"type":"integer","title":"Last30D"}},"type":"object","required":["open","last30d"],"title":"GiveawaysSummary"},"GuardOut":{"properties":{"code":{"anyOf":[{"type":"string","enum":["app","email"]},{"type":"null"}],"title":"Code"},"email_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Domain"},"confirm_in_app":{"type":"boolean","title":"Confirm In App"},"confirm_by_email":{"type":"boolean","title":"Confirm By Email"}},"type":"object","required":["code","email_domain","confirm_in_app","confirm_by_email"],"title":"GuardOut"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HistorySeriesOut":{"properties":{"store":{"$ref":"#/components/schemas/StoreRefOut"},"platform":{"type":"string","title":"Platform"},"role":{"type":"string","title":"Role"},"converted":{"type":"boolean","title":"Converted"},"quotedCurrency":{"type":"string","title":"Quotedcurrency"},"points":{"items":{"$ref":"#/components/schemas/DailyPointOut"},"type":"array","title":"Points"}},"type":"object","required":["store","platform","role","converted","quotedCurrency","points"],"title":"HistorySeriesOut"},"IngestOfferIn":{"properties":{"storeCode":{"type":"string","title":"Storecode"},"gameSlug":{"type":"string","title":"Gameslug"},"platform":{"type":"string","title":"Platform"},"region":{"type":"string","title":"Region"},"edition":{"type":"string","title":"Edition"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"priceCents":{"type":"integer","title":"Pricecents"},"currency":{"type":"string","title":"Currency","default":"EUR"},"listPriceCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Listpricecents"},"feeCents":{"type":"integer","title":"Feecents","default":0},"inStock":{"type":"boolean","title":"Instock","default":true},"sellerRating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sellerrating"},"externalId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Externalid"},"freeToKeep":{"type":"boolean","title":"Freetokeep","default":false}},"type":"object","required":["storeCode","gameSlug","platform","region","edition","priceCents"],"title":"IngestOfferIn","description":"One observation from a scraper."},"IngestResultOut":{"properties":{"received":{"type":"integer","title":"Received"},"offersCreated":{"type":"integer","title":"Offerscreated"},"offersUpdated":{"type":"integer","title":"Offersupdated"},"priceChangesRecorded":{"type":"integer","title":"Pricechangesrecorded"},"unknownGames":{"items":{"type":"string"},"type":"array","title":"Unknowngames","default":[]},"unknownStores":{"items":{"type":"string"},"type":"array","title":"Unknownstores","default":[]}},"type":"object","required":["received","offersCreated","offersUpdated","priceChangesRecorded"],"title":"IngestResultOut"},"LanguageOut":{"properties":{"code":{"type":"string","title":"Code"},"interface":{"type":"boolean","title":"Interface"},"audio":{"type":"boolean","title":"Audio"},"subtitles":{"type":"boolean","title":"Subtitles"}},"type":"object","required":["code","interface","audio","subtitles"],"title":"LanguageOut"},"ListOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"ListOut","description":"One of this site's email lists: what `?list=` takes, and its name."},"MeOut":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"emailVerified":{"type":"boolean","title":"Emailverified"},"twoFactor":{"type":"boolean","title":"Twofactor"},"methods":{"$ref":"#/components/schemas/MethodsOut"},"platforms":{"items":{"type":"string"},"type":"array","title":"Platforms"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"}},"type":"object","required":["id","username","email","emailVerified","twoFactor","methods","platforms","createdAt"],"title":"MeOut","description":"The signed-in account, as `/api/account/me` and every sign-in answer it."},"MethodsOut":{"properties":{"password":{"type":"boolean","title":"Password"},"google":{"type":"boolean","title":"Google"},"steam":{"type":"boolean","title":"Steam"},"epic":{"type":"boolean","title":"Epic"}},"type":"object","required":["password","google","steam","epic"],"title":"MethodsOut","description":"The ways this account can sign in."},"MyReviewOut":{"properties":{"id":{"type":"integer","title":"Id"},"rating":{"type":"number","title":"Rating"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"hidden":{"type":"boolean","title":"Hidden"}},"type":"object","required":["id","rating","body","createdAt","updatedAt","hidden"],"title":"MyReviewOut","description":"The signed-in user's own review of a product, as its author sees it."},"NewsCardOut":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"tag":{"type":"string","title":"Tag"},"author":{"type":"string","title":"Author"},"publishedAt":{"type":"string","format":"date-time","title":"Publishedat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"readingMinutes":{"type":"integer","title":"Readingminutes"},"cover":{"anyOf":[{"$ref":"#/components/schemas/NewsCoverOut"},{"type":"null"}]},"videoId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Videoid"},"lang":{"type":"string","title":"Lang","default":"en"}},"type":"object","required":["slug","title","dek","tag","author","publishedAt","updatedAt","readingMinutes"],"title":"NewsCardOut","description":"A post as the index page and the related list draw it."},"NewsCoverOut":{"properties":{"src":{"type":"string","title":"Src"},"srcsetAvif":{"type":"string","title":"Srcsetavif"},"srcsetWebp":{"type":"string","title":"Srcsetwebp"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"}},"type":"object","required":["src","srcsetAvif","srcsetWebp","width","height"],"title":"NewsCoverOut","description":"A post's picture: `src` a 960-wide WebP, the two `srcset`s the widths in\nAVIF and WebP (`url 480w, url 960w, url 1600w` for a stored cover; a\ngame's own widths when it is borrowed), and the frame's size for the box a\npage reserves. Pages crop it to 16:9. Site-relative, like `coverBase`."},"NewsGameRefOut":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"}},"type":"object","required":["id","slug","title"],"title":"NewsGameRefOut","description":"A rail game as the admin's picker shows it."},"NewsListOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/NewsCardOut"},"type":"array","title":"Items"},"page":{"type":"integer","title":"Page"},"pageSize":{"type":"integer","title":"Pagesize"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["items","page","pageSize","total"],"title":"NewsListOut"},"NewsPostOut":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"tag":{"type":"string","title":"Tag"},"author":{"type":"string","title":"Author"},"publishedAt":{"type":"string","format":"date-time","title":"Publishedat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"readingMinutes":{"type":"integer","title":"Readingminutes"},"cover":{"anyOf":[{"$ref":"#/components/schemas/NewsCoverOut"},{"type":"null"}]},"videoId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Videoid"},"lang":{"type":"string","title":"Lang","default":"en"},"blocks":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Blocks"},"games":{"items":{"$ref":"#/components/schemas/GameCardOut"},"type":"array","title":"Games"},"mentions":{"additionalProperties":{"$ref":"#/components/schemas/GameCardOut"},"type":"object","title":"Mentions","default":{}},"related":{"items":{"$ref":"#/components/schemas/NewsCardOut"},"type":"array","title":"Related"}},"type":"object","required":["slug","title","dek","tag","author","publishedAt","updatedAt","readingMinutes","blocks","games","related"],"title":"NewsPostOut","description":"One post: its card, its body as blocks, its rail of games and what to read next."},"NewsPreviewIn":{"properties":{"bodyMd":{"type":"string","title":"Bodymd","default":""}},"type":"object","title":"NewsPreviewIn"},"NewsPreviewOut":{"properties":{"blocks":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Blocks"},"readingMinutes":{"type":"integer","title":"Readingminutes"}},"type":"object","required":["blocks","readingMinutes"],"title":"NewsPreviewOut"},"NewsTranslationOut":{"properties":{"locale":{"type":"string","title":"Locale"},"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"bodyMd":{"type":"string","title":"Bodymd"},"readingMinutes":{"type":"integer","title":"Readingminutes"},"status":{"type":"string","enum":["current","outdated"],"title":"Status"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"sourceAtSave":{"$ref":"#/components/schemas/NewsTranslationTextOut"}},"type":"object","required":["locale","title","dek","bodyMd","readingMinutes","status","updatedAt","sourceAtSave"],"title":"NewsTranslationOut","description":"One translation of a post, as the Midfeed admin edits it."},"NewsTranslationSourceOut":{"properties":{"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"bodyMd":{"type":"string","title":"Bodymd"},"hash":{"type":"string","title":"Hash"}},"type":"object","required":["title","dek","bodyMd","hash"],"title":"NewsTranslationSourceOut","description":"The English a translation is made from, and its `news.source_hash`."},"NewsTranslationTextOut":{"properties":{"title":{"type":"string","title":"Title"},"dek":{"type":"string","title":"Dek"},"bodyMd":{"type":"string","title":"Bodymd"}},"type":"object","required":["title","dek","bodyMd"],"title":"NewsTranslationTextOut","description":"A post's English words: its title, summary and body."},"NewsTranslationsOut":{"properties":{"source":{"$ref":"#/components/schemas/NewsTranslationSourceOut"},"items":{"items":{"$ref":"#/components/schemas/NewsTranslationOut"},"type":"array","title":"Items"},"missing":{"items":{"type":"string"},"type":"array","title":"Missing"}},"type":"object","required":["source","items","missing"],"title":"NewsTranslationsOut","description":"A post's English, its translations, and the languages it has none in."},"NotificationItemsOut":{"properties":{"giveaways":{"type":"boolean","title":"Giveaways"},"sales":{"type":"boolean","title":"Sales"},"priceAlerts":{"type":"boolean","title":"Pricealerts"},"news":{"type":"boolean","title":"News"}},"type":"object","required":["giveaways","sales","priceAlerts","news"],"title":"NotificationItemsOut","description":"The account page's email switches, each on or off, by the key `POST\n/api/account/notifications` takes. The account and security emails are\nnot among them: those always go."},"NotificationsOut":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"emailVerified":{"type":"boolean","title":"Emailverified"},"items":{"$ref":"#/components/schemas/NotificationItemsOut"}},"type":"object","required":["email","emailVerified","items"],"title":"NotificationsOut","description":"`/api/account/notifications`: the switches, and the address they are about."},"OfferCardOut":{"properties":{"store":{"$ref":"#/components/schemas/StoreRefOut"},"currency":{"type":"string","title":"Currency"},"listPriceCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Listpricecents"},"feeCents":{"type":"integer","title":"Feecents"},"totalCents":{"type":"integer","title":"Totalcents"},"platform":{"type":"string","title":"Platform"},"inStock":{"type":"boolean","title":"Instock"}},"type":"object","required":["store","currency","listPriceCents","feeCents","totalCents","platform","inStock"],"title":"OfferCardOut","description":"The part of an offer a CARD renders, and nothing else.\n\nA listing sends one of these per offer per game, and the cards read only\nfour things from the set: the cheapest total, its currency and store, and\nthe ceiling the saving badge is measured against. Sending the full\n`OfferOut` there put 24,901 bytes of offer into a 27,704-byte `/deals`\nresponse — **89% of the payload** — to compute those four numbers, and it\nsat on the critical path: nothing paints until it arrives.\n\nField NAMES match `OfferOut` exactly so `bestOfferIn` and `priceSpread` in\nthe browser work against either shape unchanged. The detail page keeps the\nfull object, which is where the dropped fields are actually rendered."},"OfferOut":{"properties":{"store":{"$ref":"#/components/schemas/StoreRefOut"},"currency":{"type":"string","title":"Currency"},"listPriceCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Listpricecents"},"feeCents":{"type":"integer","title":"Feecents"},"totalCents":{"type":"integer","title":"Totalcents"},"platform":{"type":"string","title":"Platform"},"inStock":{"type":"boolean","title":"Instock"},"id":{"type":"string","title":"Id"},"priceCents":{"type":"integer","title":"Pricecents"},"freeToKeep":{"type":"boolean","title":"Freetokeep","default":false},"couponCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Couponcode"},"couponDiscount":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coupondiscount"},"region":{"type":"string","title":"Region"},"edition":{"type":"string","title":"Edition"},"regionLocked":{"type":"boolean","title":"Regionlocked","default":false},"sellerRating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sellerrating"},"checkedAt":{"type":"string","format":"date-time","title":"Checkedat"},"outUrl":{"type":"string","title":"Outurl"}},"type":"object","required":["store","currency","listPriceCents","feeCents","totalCents","platform","inStock","id","priceCents","region","edition","checkedAt","outUrl"],"title":"OfferOut","description":"Everything about an offer, for the page that renders the offer table."},"PlatformCounts":{"properties":{"steam":{"type":"integer","title":"Steam"},"epic":{"type":"integer","title":"Epic"},"gog":{"type":"integer","title":"Gog"}},"type":"object","required":["steam","epic","gog"],"title":"PlatformCounts"},"PostIn":{"properties":{"body":{"type":"string","maxLength":5000,"minLength":1,"title":"Body"},"media":{"type":"string","enum":["none","image","video"],"title":"Media","default":"none"},"networks":{"items":{"type":"string"},"type":"array","title":"Networks","default":[]},"stories":{"items":{"type":"string"},"type":"array","title":"Stories","default":[]},"game_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Game Id"},"hashtags":{"type":"boolean","title":"Hashtags","default":true},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"},"video_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Video Id"},"on_bio":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Bio"},"x_link":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"X Link"},"tiktok_mode":{"anyOf":[{"type":"string","enum":["inbox","private"]},{"type":"null"}],"title":"Tiktok Mode"},"template":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Template"}},"type":"object","required":["body"],"title":"PostIn"},"PostOut":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"type":"string","title":"Kind"},"game_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Game Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"body":{"type":"string","title":"Body"},"media":{"type":"string","title":"Media"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"price_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Cents"},"was_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Was Cents"},"store_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Store Count"},"created_by":{"type":"string","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"on_bio":{"type":"boolean","title":"On Bio","default":true},"x_link":{"type":"boolean","title":"X Link","default":false},"tiktok_mode":{"type":"string","enum":["inbox","private"],"title":"Tiktok Mode","default":"inbox"},"template":{"type":"string","title":"Template","default":"minimal"},"video":{"anyOf":[{"$ref":"#/components/schemas/gcg__routers__social__VideoOut"},{"type":"null"}]},"deliveries":{"items":{"$ref":"#/components/schemas/DeliveryOut"},"type":"array","title":"Deliveries"},"planned_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Planned At"}},"type":"object","required":["id","kind","game_id","title","link","body","media","currency","price_cents","was_cents","store_count","created_by","created_at","deliveries"],"title":"PostOut"},"PostPatch":{"properties":{"on_bio":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Bio"},"x_link":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"X Link"},"tiktok_mode":{"anyOf":[{"type":"string","enum":["inbox","private"]},{"type":"null"}],"title":"Tiktok Mode"},"template":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Template"}},"type":"object","title":"PostPatch","description":"Only what changes. `on_bio` any time; the rest while its network waits."},"PreviewOut":{"properties":{"game_id":{"type":"integer","title":"Game Id"},"title":{"type":"string","title":"Title"},"link":{"type":"string","title":"Link"},"kind":{"type":"string","title":"Kind"},"currency":{"type":"string","title":"Currency"},"price_cents":{"type":"integer","title":"Price Cents"},"was_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Was Cents"},"off_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Off Pct"},"stores":{"type":"integer","title":"Stores"},"body":{"type":"string","title":"Body"},"captions":{"items":{"$ref":"#/components/schemas/CaptionOut"},"type":"array","title":"Captions"},"store":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Store"},"official":{"type":"boolean","title":"Official","default":false},"steam_price_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Steam Price Cents"},"reviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reviews"},"review_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Review Pct"},"rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rank"},"chart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chart"}},"type":"object","required":["game_id","title","link","kind","currency","price_cents","was_cents","off_pct","stores","body","captions"],"title":"PreviewOut"},"PriceHistoryOut":{"properties":{"currency":{"type":"string","title":"Currency"},"today":{"type":"string","format":"date","title":"Today"},"windowDays":{"type":"integer","title":"Windowdays"},"platforms":{"items":{"type":"string"},"type":"array","title":"Platforms"},"best":{"items":{"$ref":"#/components/schemas/BestPointOut"},"type":"array","title":"Best"},"series":{"items":{"$ref":"#/components/schemas/HistorySeriesOut"},"type":"array","title":"Series"},"foreign":{"anyOf":[{"$ref":"#/components/schemas/ForeignTrackOut"},{"type":"null"}]},"conversion":{"anyOf":[{"$ref":"#/components/schemas/ConversionOut"},{"type":"null"}]}},"type":"object","required":["currency","today","windowDays","platforms","best","series"],"title":"PriceHistoryOut"},"ProvidersOut":{"properties":{"accounts":{"type":"boolean","title":"Accounts"},"google":{"type":"boolean","title":"Google"},"steam":{"type":"boolean","title":"Steam"},"epic":{"type":"boolean","title":"Epic"}},"type":"object","required":["accounts","google","steam","epic"],"title":"ProvidersOut","description":"Which sign-ins this deployment offers; `accounts` false means none at all."},"PublisherLookupOut":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"logo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo"},"logoDark":{"type":"boolean","title":"Logodark","default":false}},"type":"object","required":["slug","name"],"title":"PublisherLookupOut","description":"One publisher found by name, for tools that want its logo (`/publishers/lookup`).\n\nNo game count: the lookup finds a studio whether or not we sell a game of\nit, so a count would be a second query for a number nobody asked for."},"PublisherOut":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"games":{"type":"integer","title":"Games"},"logo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo"},"logoDark":{"type":"boolean","title":"Logodark","default":false}},"type":"object","required":["slug","name","games"],"title":"PublisherOut","description":"A publisher, with how many priced games we carry for it."},"QrOut":{"properties":{"challenge_url":{"type":"string","title":"Challenge Url"},"image":{"type":"string","title":"Image"}},"type":"object","required":["challenge_url","image"],"title":"QrOut"},"QueueOut":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/PostOut"},"type":"array","title":"Rows"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts","default":{}}},"type":"object","required":["rows","total","limit","offset"],"title":"QueueOut"},"RecoveryCodesOut":{"properties":{"recoveryCodes":{"items":{"type":"string"},"type":"array","title":"Recoverycodes"}},"type":"object","required":["recoveryCodes"],"title":"RecoveryCodesOut","description":"Eight codes, `xxxxx-xxxxx`, shown once."},"RequirementLineOut":{"properties":{"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"},"label":{"type":"string","title":"Label"},"value":{"type":"string","title":"Value"}},"type":"object","required":["key","label","value"],"title":"RequirementLineOut"},"RequirementsOut":{"properties":{"system":{"type":"string","enum":["windows","mac","linux"],"title":"System"},"minimum":{"items":{"$ref":"#/components/schemas/RequirementLineOut"},"type":"array","title":"Minimum","default":[]},"recommended":{"items":{"$ref":"#/components/schemas/RequirementLineOut"},"type":"array","title":"Recommended","default":[]}},"type":"object","required":["system"],"title":"RequirementsOut"},"ReviewChangeOut":{"properties":{"review":{"anyOf":[{"$ref":"#/components/schemas/MyReviewOut"},{"type":"null"}]},"summary":{"$ref":"#/components/schemas/ReviewSummaryOut"}},"type":"object","required":["review","summary"],"title":"ReviewChangeOut","description":"What a write answers: the review as it now is (None once deleted), and\nthe product's rating with the change counted. The public list is cached\nat the edge for a minute, so this is how the page shows the author their\nchange at once rather than asking `/reviews` again."},"ReviewItemOut":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"rating":{"type":"number","title":"Rating"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"}},"type":"object","required":["id","username","rating","body","createdAt","updatedAt"],"title":"ReviewItemOut","description":"One visible review, as the list shows it."},"ReviewPageOut":{"properties":{"summary":{"$ref":"#/components/schemas/ReviewSummaryOut"},"items":{"items":{"$ref":"#/components/schemas/ReviewItemOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["summary","items","total"],"title":"ReviewPageOut","description":"`GET /api/games/{slug}/reviews`: the rating, and a page of the reviews, newest first."},"ReviewSummaryOut":{"properties":{"average":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average"},"count":{"type":"integer","title":"Count"},"distribution":{"items":{"type":"integer"},"type":"array","title":"Distribution"}},"type":"object","required":["average","count","distribution"],"title":"ReviewSummaryOut","description":"The GCG rating of a product, over its visible reviews (`service.Summary`)."},"ScreenshotOut":{"properties":{"thumb":{"type":"string","title":"Thumb"},"full":{"type":"string","title":"Full"}},"type":"object","required":["thumb","full"],"title":"ScreenshotOut","description":"One screenshot, at the two sizes the gallery uses."},"SearchOut":{"properties":{"games":{"items":{"$ref":"#/components/schemas/GameCardOut"},"type":"array","title":"Games","default":[]},"franchises":{"items":{"$ref":"#/components/schemas/FranchiseOut"},"type":"array","title":"Franchises","default":[]},"publishers":{"items":{"$ref":"#/components/schemas/PublisherOut"},"type":"array","title":"Publishers","default":[]}},"type":"object","title":"SearchOut","description":"Everything a term matched, split by what kind of thing it is.\n\nThree lists rather than one ranked blend: a franchise and a game are not\ncomparable by price, and interleaving them would force an ordering that\nmeans nothing. The client shows a column per non-empty list."},"Sends":{"properties":{"total":{"type":"integer","title":"Total"},"lastAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastat"},"last30d":{"type":"integer","title":"Last30D"}},"type":"object","required":["total","lastAt","last30d"],"title":"Sends"},"SessionOut":{"properties":{"id":{"type":"integer","title":"Id"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"lastSeenAt":{"type":"string","format":"date-time","title":"Lastseenat"},"current":{"type":"boolean","title":"Current"}},"type":"object","required":["id","createdAt","lastSeenAt","current"],"title":"SessionOut","description":"One signed-in browser: dates and whether it is this one. No device, no address."},"SettingsOut":{"properties":{"paused":{"type":"boolean","title":"Paused"},"currency":{"type":"string","title":"Currency"},"drop_min_pct":{"type":"integer","title":"Drop Min Pct"},"drop_min_cents":{"type":"integer","title":"Drop Min Cents"},"min_stores":{"type":"integer","title":"Min Stores"},"baseline_days":{"type":"integer","title":"Baseline Days"},"min_history_days":{"type":"integer","title":"Min History Days"},"cooldown_days":{"type":"integer","title":"Cooldown Days"},"per_run_cap":{"type":"integer","title":"Per Run Cap"},"min_gap_minutes":{"type":"integer","title":"Min Gap Minutes"},"daily_cap":{"type":"integer","title":"Daily Cap"},"youtube_daily_cap":{"type":"integer","title":"Youtube Daily Cap","default":6},"hashtags":{"type":"string","title":"Hashtags"},"stories":{"type":"boolean","title":"Stories"},"on_bio":{"type":"boolean","title":"On Bio"},"x_link":{"type":"boolean","title":"X Link"},"tiktok_mode":{"type":"string","enum":["inbox","private"],"title":"Tiktok Mode"},"template":{"type":"string","title":"Template"},"tiktok_access":{"type":"string","enum":["inbox","direct"],"title":"Tiktok Access"},"auto_kinds":{"items":{"type":"string"},"type":"array","title":"Auto Kinds"},"min_reviews":{"type":"integer","title":"Min Reviews"},"min_review_pct":{"type":"integer","title":"Min Review Pct"},"hot_min_reviews":{"type":"integer","title":"Hot Min Reviews"},"deal_min_pct":{"type":"integer","title":"Deal Min Pct"},"prefer_store":{"type":"string","title":"Prefer Store"},"prefer_tolerance_pct":{"type":"integer","title":"Prefer Tolerance Pct"},"slot_times":{"type":"string","title":"Slot Times"},"deal_of_day_time":{"type":"string","title":"Deal Of Day Time","default":"18:00"}},"type":"object","required":["paused","currency","drop_min_pct","drop_min_cents","min_stores","baseline_days","min_history_days","cooldown_days","per_run_cap","min_gap_minutes","daily_cap","hashtags","stories","on_bio","x_link","tiktok_mode","template","tiktok_access","auto_kinds","min_reviews","min_review_pct","hot_min_reviews","deal_min_pct","prefer_store","prefer_tolerance_pct","slot_times"],"title":"SettingsOut"},"SettingsPatch":{"properties":{"paused":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Paused"},"drop_min_pct":{"anyOf":[{"type":"integer","maximum":95.0,"minimum":5.0},{"type":"null"}],"title":"Drop Min Pct"},"drop_min_cents":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":0.0},{"type":"null"}],"title":"Drop Min Cents"},"min_stores":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":1.0},{"type":"null"}],"title":"Min Stores"},"baseline_days":{"anyOf":[{"type":"integer","maximum":90.0,"minimum":7.0},{"type":"null"}],"title":"Baseline Days"},"min_history_days":{"anyOf":[{"type":"integer","maximum":90.0,"minimum":1.0},{"type":"null"}],"title":"Min History Days"},"cooldown_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Cooldown Days"},"per_run_cap":{"anyOf":[{"type":"integer","maximum":20.0,"minimum":1.0},{"type":"null"}],"title":"Per Run Cap"},"min_gap_minutes":{"anyOf":[{"type":"integer","maximum":1440.0,"minimum":5.0},{"type":"null"}],"title":"Min Gap Minutes"},"daily_cap":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":1.0},{"type":"null"}],"title":"Daily Cap"},"youtube_daily_cap":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":0.0},{"type":"null"}],"title":"Youtube Daily Cap"},"hashtags":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Hashtags"},"stories":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stories"},"on_bio":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Bio"},"x_link":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"X Link"},"tiktok_mode":{"anyOf":[{"type":"string","enum":["inbox","private"]},{"type":"null"}],"title":"Tiktok Mode"},"template":{"anyOf":[{"type":"string","maxLength":24},{"type":"null"}],"title":"Template"},"tiktok_access":{"anyOf":[{"type":"string","enum":["inbox","direct"]},{"type":"null"}],"title":"Tiktok Access"},"auto_kinds":{"anyOf":[{"items":{"type":"string","enum":["price_drop","new_release","free_to_keep","top_reviewed","steam_hot","deal_of_day"]},"type":"array"},{"type":"null"}],"title":"Auto Kinds"},"min_reviews":{"anyOf":[{"type":"integer","maximum":1000000.0,"minimum":0.0},{"type":"null"}],"title":"Min Reviews"},"min_review_pct":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Min Review Pct"},"hot_min_reviews":{"anyOf":[{"type":"integer","maximum":1000000.0,"minimum":0.0},{"type":"null"}],"title":"Hot Min Reviews"},"deal_min_pct":{"anyOf":[{"type":"integer","maximum":95.0,"minimum":0.0},{"type":"null"}],"title":"Deal Min Pct"},"prefer_store":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Prefer Store"},"prefer_tolerance_pct":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":0.0},{"type":"null"}],"title":"Prefer Tolerance Pct"},"slot_times":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Slot Times"},"deal_of_day_time":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Deal Of Day Time"}},"type":"object","title":"SettingsPatch","description":"Every field optional: send only what changes. Ranges are the sane ones."},"SignOutOut":{"properties":{"account":{"type":"null","title":"Account"},"revoked":{"type":"boolean","title":"Revoked"}},"type":"object","required":["revoked"],"title":"SignOutOut"},"SpecsOut":{"properties":{"platforms":{"items":{"type":"string","enum":["windows","mac","linux"]},"type":"array","title":"Platforms","default":[]},"requirements":{"items":{"$ref":"#/components/schemas/RequirementsOut"},"type":"array","title":"Requirements","default":[]},"languages":{"items":{"$ref":"#/components/schemas/LanguageOut"},"type":"array","title":"Languages","default":[]}},"type":"object","title":"SpecsOut","description":"What a PC game runs on and in, from Steam. The page shows it beside PC\nkeys only: an Xbox or PlayStation key has none of these requirements."},"StartOut":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"StartOut"},"StatsBioBrowse":{"properties":{"clicks":{"type":"integer","title":"Clicks"},"by_source":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Source"}},"type":"object","required":["clicks","by_source"],"title":"StatsBioBrowse","description":"The bio page's \"browse every deal\", which is a tap but not a card."},"StatsBioCard":{"properties":{"post_id":{"type":"integer","title":"Post Id"},"title":{"type":"string","title":"Title"},"slug":{"type":"string","title":"Slug"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"clicks":{"type":"integer","title":"Clicks"},"by_source":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Source"}},"type":"object","required":["post_id","title","slug","sent_at","clicks","by_source"],"title":"StatsBioCard","description":"One card on the bio page, and who tapped it from where."},"StatsBioSource":{"properties":{"source":{"type":"string","title":"Source"},"visits":{"type":"integer","title":"Visits"},"clicks":{"type":"integer","title":"Clicks"}},"type":"object","required":["source","visits","clicks"],"title":"StatsBioSource"},"StatsDay":{"properties":{"day":{"type":"string","format":"date","title":"Day"},"posts":{"type":"integer","title":"Posts"},"views":{"type":"integer","title":"Views"},"social_clicks":{"type":"integer","title":"Social Clicks"},"bio_visits":{"type":"integer","title":"Bio Visits"},"store_clicks":{"type":"integer","title":"Store Clicks"},"stores":{"items":{"$ref":"#/components/schemas/StatsDayStore"},"type":"array","title":"Stores"}},"type":"object","required":["day","posts","views","social_clicks","bio_visits","store_clicks"],"title":"StatsDay"},"StatsDayStore":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"accent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent"},"tagged":{"type":"boolean","title":"Tagged"},"clicks":{"type":"integer","title":"Clicks"}},"type":"object","required":["code","name","accent","tagged","clicks"],"title":"StatsDayStore","description":"One store's clicks on one day: the breakdown of that day's bar."},"StatsFreeClaim":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"claims":{"type":"integer","title":"Claims"},"plays":{"type":"integer","title":"Plays"},"stores":{"additionalProperties":{"type":"integer"},"type":"object","title":"Stores"},"pages":{"$ref":"#/components/schemas/StatsFreeClaimPages"}},"type":"object","required":["slug","title","claims","plays","stores","pages"],"title":"StatsFreeClaim","description":"One game's free button on the free-to-play or game page, pressed in the window."},"StatsFreeClaimPages":{"properties":{"free_to_play":{"type":"integer","title":"Free To Play"},"game_page":{"type":"integer","title":"Game Page"}},"type":"object","required":["free_to_play","game_page"],"title":"StatsFreeClaimPages","description":"Claims and plays together, by the page pressed on (`stats.FREE_CLAIM_PAGES`):\nthe free-to-play page (its giveaways and free-to-play lists) and the game\npage's own free buttons."},"StatsFreeClaimTotals":{"properties":{"claims":{"type":"integer","title":"Claims"},"plays":{"type":"integer","title":"Plays"},"pages":{"$ref":"#/components/schemas/StatsFreeClaimPages"}},"type":"object","required":["claims","plays","pages"],"title":"StatsFreeClaimTotals"},"StatsFreeClaims":{"properties":{"counting_since":{"type":"string","format":"date","title":"Counting Since"},"totals":{"$ref":"#/components/schemas/StatsFreeClaimTotals"},"games":{"items":{"$ref":"#/components/schemas/StatsFreeClaim"},"type":"array","title":"Games"}},"type":"object","required":["counting_since","totals","games"],"title":"StatsFreeClaims","description":"The \"Claim\" and \"Play now\" presses on the free-to-play page and the game\npage (`free_claims`, gcg/routers/track.py), most pressed first. `counting_since` is the day\nthe counter started (`stats._free_claims`): nothing before it was\ncounted, which an empty table should say. The name is the Midfeed admin's\ncontract (web-admin/src/api.ts `GcgFreeClaims.counting_since`; its proxy\npasses this JSON through untouched, so a rename on either side alone\nblanks the date there — tests/test_free_claims.py pins the key set). A\nbare date, which the admin shows as the same day in every timezone."},"StatsGame":{"properties":{"title":{"type":"string","title":"Title"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"clicks":{"type":"integer","title":"Clicks"}},"type":"object","required":["title","slug","clicks"],"title":"StatsGame"},"StatsLink":{"properties":{"network":{"type":"string","title":"Network"},"placement":{"type":"string","title":"Placement"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"likes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likes"},"comments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comments"},"shares":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shares"}},"type":"object","required":["network","placement","url","views","likes","comments","shares"],"title":"StatsLink"},"StatsMcp":{"properties":{"counting_since":{"type":"string","format":"date","title":"Counting Since"},"calls":{"$ref":"#/components/schemas/StatsTotal"},"store_clicks":{"$ref":"#/components/schemas/StatsTotal"},"clients":{"items":{"$ref":"#/components/schemas/StatsMcpRow"},"type":"array","title":"Clients"},"tools":{"items":{"$ref":"#/components/schemas/StatsMcpRow"},"type":"array","title":"Tools"},"daily":{"items":{"$ref":"#/components/schemas/StatsMcpDay"},"type":"array","title":"Daily"}},"type":"object","required":["counting_since","calls","store_clicks","clients","tools","daily"],"title":"StatsMcp","description":"The MCP connector: tool calls by AI assistants and the store clicks their\nanswers' buy links led to (`stats._mcp`). `clients` are claude, chatgpt,\ngemini, grok and other, most calls first; `tools` the same by tool. Days\nare UTC. `counting_since` is the first counted day: nothing before it."},"StatsMcpDay":{"properties":{"day":{"type":"string","format":"date","title":"Day"},"calls":{"type":"integer","title":"Calls"},"store_clicks":{"type":"integer","title":"Store Clicks"}},"type":"object","required":["day","calls","store_clicks"],"title":"StatsMcpDay"},"StatsMcpRow":{"properties":{"name":{"type":"string","title":"Name"},"calls":{"type":"integer","title":"Calls"}},"type":"object","required":["name","calls"],"title":"StatsMcpRow"},"StatsNetwork":{"properties":{"network":{"type":"string","title":"Network"},"placement":{"type":"string","title":"Placement"},"posts":{"type":"integer","title":"Posts"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"engagements":{"type":"integer","title":"Engagements"},"clicks":{"type":"integer","title":"Clicks"},"last_sent":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Sent"},"last_read":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Read"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"followers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers"},"followers_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Followers At"},"followers_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Followers Note"},"channel_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel Views"},"channel_videos":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel Videos"},"channel_views_long":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel Views Long"},"channel_views_shorts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel Views Shorts"},"channel_stats_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Channel Stats At"}},"type":"object","required":["network","placement","posts","views","engagements","clicks","last_sent","last_read","note","followers","followers_at","followers_note"],"title":"StatsNetwork","description":"One network's feed posts, or its Stories: the two are counted apart."},"StatsPost":{"properties":{"post_id":{"type":"integer","title":"Post Id"},"title":{"type":"string","title":"Title"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"engagements":{"type":"integer","title":"Engagements"},"clicks":{"type":"integer","title":"Clicks"},"links":{"items":{"$ref":"#/components/schemas/StatsLink"},"type":"array","title":"Links"}},"type":"object","required":["post_id","title","slug","sent_at","views","engagements","clicks","links"],"title":"StatsPost"},"StatsStore":{"properties":{"name":{"type":"string","title":"Name"},"clicks":{"type":"integer","title":"Clicks"}},"type":"object","required":["name","clicks"],"title":"StatsStore"},"StatsTotal":{"properties":{"now":{"type":"integer","title":"Now"},"before":{"type":"integer","title":"Before"}},"type":"object","required":["now","before"],"title":"StatsTotal"},"StatusOut":{"properties":{"status":{"type":"string","enum":["ok","sent","expired","invalid","taken"],"title":"Status"}},"type":"object","required":["status"],"title":"StatusOut","description":"One word: the email routes say `sent` whatever the address's state."},"SteamReviewsOut":{"properties":{"count":{"type":"integer","title":"Count"},"percentPositive":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Percentpositive"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["count","percentPositive","label","url"],"title":"SteamReviewsOut","description":"Steam's own user reviews of the product, as its store page sums them\n(`SteamProduct.review_count`, `review_percent_positive`). Shown beside\nour rating, attributed and linked, and never presented as ours: it is\nSteam's figure about Steam's buyers."},"StoreOut":{"properties":{"id":{"type":"integer","title":"Id"},"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"domain":{"type":"string","title":"Domain"},"accent":{"type":"string","title":"Accent"},"kind":{"type":"string","title":"Kind"},"partner":{"type":"boolean","title":"Partner"}},"type":"object","required":["id","code","name","domain","accent","kind","partner"],"title":"StoreOut","description":"Public view of a store.\n\nNote what is absent: the affiliate parameter and our publisher id. Those\nstay server-side and are applied by the outbound redirect, so our\naffiliate identity never ships in the browser bundle and cannot be\nstripped, spoofed or scraped off the page."},"StorePageOut":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"domain":{"type":"string","title":"Domain"},"accent":{"type":"string","title":"Accent"},"kind":{"type":"string","title":"Kind"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"offers":{"type":"integer","title":"Offers"},"games":{"type":"integer","title":"Games"},"wins":{"type":"integer","title":"Wins"},"winsOf":{"type":"integer","title":"Winsof"},"lastCheckedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastcheckedat"},"checkOk":{"type":"boolean","title":"Checkok"},"fees":{"type":"integer","title":"Fees"},"couponCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Couponcode"},"couponDiscount":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coupondiscount"},"couponOffers":{"type":"integer","title":"Couponoffers"},"cheapest":{"items":{"$ref":"#/components/schemas/GameCardOut"},"type":"array","title":"Cheapest"}},"type":"object","required":["code","name","domain","accent","kind","currency","offers","games","wins","winsOf","lastCheckedAt","checkOk","fees","couponCode","couponDiscount","couponOffers","cheapest"],"title":"StorePageOut","description":"One store's page (`catalog.store_page`): its identity, the stores table's\nfigures for it, and the games it is cheapest on.\n\nDeclared beside its one route rather than in gcg/schemas.py, and mirrored\nby `StorePageData` in app/src/lib/storePage.ts."},"StoreRefOut":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"accent":{"type":"string","title":"Accent"},"kind":{"type":"string","title":"Kind"}},"type":"object","required":["code","name","accent","kind"],"title":"StoreRefOut","description":"The store fields a client needs to render an offer.\n\nEmbedded in each offer rather than referenced by id: it removes a\nclient-side join and a second request, and six short records across a\nresponse is nothing next to the round trip it saves."},"StoreStatsOut":{"properties":{"code":{"type":"string","title":"Code"},"offers":{"type":"integer","title":"Offers"},"games":{"type":"integer","title":"Games"},"cheapestCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cheapestcents"},"wins":{"type":"integer","title":"Wins"},"winsOf":{"type":"integer","title":"Winsof"},"lastCheckedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastcheckedat"},"everFetched":{"type":"boolean","title":"Everfetched","default":false},"checkOk":{"type":"boolean","title":"Checkok","default":true}},"type":"object","required":["code","offers","games","wins","winsOf"],"title":"StoreStatsOut","description":"One row of the stores table, measured over the whole catalogue.\n\n`wins_of` is carried per row rather than left to the client to infer from\nthe rows it happens to have: a store with no offers contributes no games,\nso summing the `games` column would give a denominator that changes\nwhenever a store is hidden, and \"2,951 of 3,752\" would quietly become a\ndifferent claim from \"2,951 of 4,339\"."},"SubscriberOut":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"status":{"type":"string","title":"Status"},"source":{"type":"string","title":"Source"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"confirmedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmedat"},"unsubscribedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Unsubscribedat"},"accountSub":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accountsub"},"alertsSent":{"type":"integer","title":"Alertssent"},"lastAlertAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastalertat"}},"type":"object","required":["id","email","status","source","createdAt","confirmedAt","unsubscribedAt","alertsSent","lastAlertAt"],"title":"SubscriberOut"},"SubscribersOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SubscriberOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"counts":{"$ref":"#/components/schemas/TabCounts"},"listId":{"type":"string","enum":["giveaways","sales","news"],"title":"Listid"}},"type":"object","required":["items","total","limit","offset","counts","listId"],"title":"SubscribersOut"},"SummaryOut":{"properties":{"list":{"type":"string","title":"List"},"listId":{"type":"string","enum":["giveaways","sales","news"],"title":"Listid"},"name":{"type":"string","title":"Name"},"counts":{"$ref":"#/components/schemas/Counts"},"joined30d":{"type":"integer","title":"Joined30D"},"left30d":{"type":"integer","title":"Left30D"},"breakdown":{"$ref":"#/components/schemas/Breakdown"},"sends":{"anyOf":[{"$ref":"#/components/schemas/Sends"},{"type":"null"}]},"giveaways":{"anyOf":[{"$ref":"#/components/schemas/GiveawaysSummary"},{"type":"null"}]}},"type":"object","required":["list","listId","name","counts","joined30d","left30d","breakdown","sends","giveaways"],"title":"SummaryOut"},"TabCounts":{"properties":{"all":{"type":"integer","title":"All"},"subscribed":{"type":"integer","title":"Subscribed"},"pending":{"type":"integer","title":"Pending"},"unsubscribed":{"type":"integer","title":"Unsubscribed"}},"type":"object","required":["all","subscribed","pending","unsubscribed"],"title":"TabCounts","description":"Per status, for the same search: what each filter chip would show."},"TemplateOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"kind":{"type":"string","title":"Kind"},"about":{"type":"string","title":"About"},"seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seconds"},"still":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Still"},"available":{"type":"boolean","title":"Available"},"why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why"},"free_only":{"type":"boolean","title":"Free Only","default":false}},"type":"object","required":["id","name","kind","about","seconds","still","available","why"],"title":"TemplateOut","description":"One way to draw a post about a game (gcg.social.templates), as the picker lists it."},"TemplatesOut":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/TemplateOut"},"type":"array","title":"Templates"}},"type":"object","required":["templates"],"title":"TemplatesOut"},"TotpStartOut":{"properties":{"setup":{"type":"string","title":"Setup"},"secret":{"type":"string","title":"Secret"},"otpauth":{"type":"string","title":"Otpauth"},"qr":{"type":"string","title":"Qr"}},"type":"object","required":["setup","secret","otpauth","qr"],"title":"TotpStartOut","description":"An enrolment begun: what to scan or type, and the blob the confirm step hands back."},"UrlOut":{"properties":{"label":{"type":"string","title":"Label"},"url":{"type":"string","title":"Url"}},"type":"object","required":["label","url"],"title":"UrlOut"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WaitIn":{"properties":{"attempt":{"type":"string","title":"Attempt"}},"type":"object","required":["attempt"],"title":"WaitIn"},"WishlistBestOut":{"properties":{"totalCents":{"type":"integer","title":"Totalcents"},"currency":{"type":"string","title":"Currency"},"store":{"$ref":"#/components/schemas/StoreRefOut"}},"type":"object","required":["totalCents","currency","store"],"title":"WishlistBestOut","description":"The cheapest usable offer for the item (gcg/wishlist/pricing.py): in\nstock, keyed for a region covering the item's country, fees included, in\nthe item's own currency. Never converted."},"WishlistItemOut":{"properties":{"id":{"type":"integer","title":"Id"},"game":{"$ref":"#/components/schemas/GameCardOut"},"targetCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Targetcents"},"currency":{"type":"string","title":"Currency"},"country":{"type":"string","title":"Country"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"best":{"anyOf":[{"$ref":"#/components/schemas/WishlistBestOut"},{"type":"null"}]},"reached":{"type":"boolean","title":"Reached"}},"type":"object","required":["id","game","targetCents","currency","country","createdAt","best","reached"],"title":"WishlistItemOut","description":"One wishlisted game, priced as a card for the item's own market."},"WishlistOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WishlistItemOut"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"WishlistOut","description":"`GET /api/account/wishlist`: every item, newest first."},"WishlistRefOut":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"targetCents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Targetcents"},"currency":{"type":"string","title":"Currency"}},"type":"object","required":["id","slug","targetCents","currency"],"title":"WishlistRefOut","description":"The light shape the hearts read: which games, by slug, and their targets."},"WishlistRefsOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WishlistRefOut"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"WishlistRefsOut","description":"`GET /api/account/wishlist/refs`: every item, newest first, as a ref."},"gcg__accounts__schemas__SignInOut":{"properties":{"status":{"type":"string","enum":["ok","totp"],"title":"Status"},"user":{"anyOf":[{"$ref":"#/components/schemas/MeOut"},{"type":"null"}]},"challenge":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Challenge"}},"type":"object","required":["status"],"title":"SignInOut","description":"`ok` with the user and the cookies set, or `totp` with a challenge to answer."},"gcg__routers__mailing__GiveawayOut":{"properties":{"id":{"type":"integer","title":"Id"},"gameId":{"type":"integer","title":"Gameid"},"title":{"type":"string","title":"Title"},"platform":{"type":"string","title":"Platform"},"claimUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimurl"},"startedAt":{"type":"string","format":"date-time","title":"Startedat"},"endsAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Endsat"},"endedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Endedat"},"alertsSent":{"type":"integer","title":"Alertssent"},"sourceId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sourceid"}},"type":"object","required":["id","gameId","title","platform","claimUrl","startedAt","endsAt","endedAt","alertsSent","sourceId"],"title":"GiveawayOut"},"gcg__routers__mailing__GiveawaysOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/gcg__routers__mailing__GiveawayOut"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"counts":{"$ref":"#/components/schemas/GiveawayCounts"}},"type":"object","required":["items","total","limit","offset","counts"],"title":"GiveawaysOut"},"gcg__routers__social__AccountOut":{"properties":{"network":{"type":"string","title":"Network"},"name":{"type":"string","title":"Name"},"handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handle"},"state":{"type":"string","title":"State"},"token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Expires At"},"paused":{"type":"boolean","title":"Paused"},"needs":{"type":"string","title":"Needs"},"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"},"link":{"type":"string","title":"Link"},"stories":{"type":"boolean","title":"Stories"},"tags":{"type":"integer","title":"Tags"},"picture":{"type":"boolean","title":"Picture"},"supported":{"type":"boolean","title":"Supported"},"setup":{"items":{"type":"string"},"type":"array","title":"Setup"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"connected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected At"},"callback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback"},"extra_urls":{"items":{"$ref":"#/components/schemas/UrlOut"},"type":"array","title":"Extra Urls"}},"type":"object","required":["network","name","handle","state","token_expires_at","paused","needs","limit","link","stories","tags","picture","supported","setup","error","connected_at","callback","extra_urls"],"title":"AccountOut"},"gcg__routers__social__StateOut":{"properties":{"settings":{"$ref":"#/components/schemas/SettingsOut"},"accounts":{"items":{"$ref":"#/components/schemas/gcg__routers__social__AccountOut"},"type":"array","title":"Accounts"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"},"site_url":{"type":"string","title":"Site Url"}},"type":"object","required":["settings","accounts","counts","site_url"],"title":"StateOut"},"gcg__routers__social__StatsOut":{"properties":{"days":{"type":"integer","title":"Days"},"totals":{"additionalProperties":{"$ref":"#/components/schemas/StatsTotal"},"type":"object","title":"Totals"},"daily":{"items":{"$ref":"#/components/schemas/StatsDay"},"type":"array","title":"Daily"},"networks":{"items":{"$ref":"#/components/schemas/StatsNetwork"},"type":"array","title":"Networks"},"bio_clicks":{"type":"integer","title":"Bio Clicks"},"bio_sources":{"items":{"$ref":"#/components/schemas/StatsBioSource"},"type":"array","title":"Bio Sources"},"bio_cards":{"items":{"$ref":"#/components/schemas/StatsBioCard"},"type":"array","title":"Bio Cards"},"bio_browse":{"$ref":"#/components/schemas/StatsBioBrowse"},"posts":{"items":{"$ref":"#/components/schemas/StatsPost"},"type":"array","title":"Posts"},"games":{"items":{"$ref":"#/components/schemas/StatsGame"},"type":"array","title":"Games"},"stores":{"items":{"$ref":"#/components/schemas/StatsStore"},"type":"array","title":"Stores"},"free_claims":{"anyOf":[{"$ref":"#/components/schemas/StatsFreeClaims"},{"type":"null"}]},"mcp":{"anyOf":[{"$ref":"#/components/schemas/StatsMcp"},{"type":"null"}]}},"type":"object","required":["days","totals","daily","networks","bio_clicks","bio_sources","bio_cards","bio_browse","posts","games","stores"],"title":"StatsOut"},"gcg__routers__social__VideoOut":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"name":{"type":"string","title":"Name"},"seconds":{"type":"number","title":"Seconds"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"},"bytes":{"type":"integer","title":"Bytes"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","url","name","seconds","width","height","bytes","created_at"],"title":"VideoOut","description":"An uploaded clip: where the networks fetch it, and what the admin measured."},"gcg__routers__steam_account__AccountOut":{"properties":{"account_name":{"type":"string","title":"Account Name"},"steam_id":{"type":"string","title":"Steam Id"},"signed_in_at":{"type":"string","format":"date-time","title":"Signed In At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"logged_on_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Logged On At"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["account_name","steam_id","signed_in_at","expires_at","logged_on_at","error"],"title":"AccountOut"},"gcg__routers__steam_account__SignInOut":{"properties":{"state":{"type":"string","enum":["signed_in","guard","qr"],"title":"State"},"account":{"anyOf":[{"$ref":"#/components/schemas/gcg__routers__steam_account__AccountOut"},{"type":"null"}]},"attempt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attempt"},"guard":{"anyOf":[{"$ref":"#/components/schemas/GuardOut"},{"type":"null"}]},"qr":{"anyOf":[{"$ref":"#/components/schemas/QrOut"},{"type":"null"}]}},"type":"object","required":["state"],"title":"SignInOut"},"gcg__routers__steam_account__StateOut":{"properties":{"account":{"anyOf":[{"$ref":"#/components/schemas/gcg__routers__steam_account__AccountOut"},{"type":"null"}]}},"type":"object","required":["account"],"title":"StateOut"},"gcg__schemas__GiveawayOut":{"properties":{"platform":{"type":"string","title":"Platform"},"claimUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimurl"},"endsAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Endsat"}},"type":"object","required":["platform"],"title":"GiveawayOut","description":"A giveaway on another store than Steam: what the claim button says and\nwhere it goes (`catalog.GIVEAWAY_ELSEWHERE`). Steam's own is the game's\n`free_to_keep`, whose link the client builds from the appid."},"gcg__schemas__GiveawaysOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GameCardOut"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"GiveawaysOut","description":"The top of the free-to-play page: the games the stores give away right\nnow, Steam's (`free_to_keep`) and the others' (`giveaway`)."},"gcg__schemas__StatsOut":{"properties":{"games":{"type":"integer","title":"Games"},"offers":{"type":"integer","title":"Offers"},"stores":{"type":"integer","title":"Stores"},"lastCheckedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastcheckedat"}},"type":"object","required":["games","offers","stores"],"title":"StatsOut"},"gcg__schemas__VideoOut":{"properties":{"kind":{"type":"string","enum":["youtube","file","hls"],"title":"Kind"},"src":{"type":"string","title":"Src"},"poster":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poster"}},"type":"object","required":["kind","src"],"title":"VideoOut","description":"The trailer that leads the gallery."}}}}