Fairly API API

Real prices for PC games, including checkout fees, and the cheapest way to play. Data is licensed CC BY 4.0, attribution: "Fairly"; game cover images are not covered by that license. Versioning and deprecation policy: https://fairly.gg/developers#versioning. Some store links are affiliate links; Fai

GitHub OpenAPI

Operations 45

GET /v1/games Search games by title
GET /v1/games/index Indexable games for the sitemap
GET /v1/games/{id} Game with offers, subscriptions, cheapest way to play and historical lows
GET /v1/games/{id}/history Authorized-store price history, daily minimum list price (before store fees)
GET /v1/discover/under/{amount} Games under $3, $5 or $10 (rounded to a round number in the requested currency)
GET /v1/discover/tiers Under tiers ($3, $5, $10) as applied in the requested currency
GET /v1/discover/historical-lows Games at their historical low
GET /v1/discover/leaving/{service} Subscription games leaving within 30 days (game-pass, ps-plus)
GET /v1/discover/play-with/{service} Games you can play with a subscription (game-pass, ps-plus)
GET /v1/discover/deal-of-the-day Deal of the day for the region
GET /v1/discover/popular Most popular games with a usable offer
GET /v1/discover/this-week This week's picks
GET /v1/subscriptions Subscription summaries by region
GET /v1/subscriptions/{code}/catalog Subscription catalog by region
GET /v1/stores Stores with type, fee rules and verification dates
GET /v1/meta/status Source health: last successful fetch, age, consecutive failures and whether it is overdue (no key needed)
POST /v1/keys Create a developer API key
POST /v1/alerts Create a price alert (double opt-in)
GET /v1/alerts/confirm Confirm a price alert
POST /v1/newsletter Subscribe to the weekly email (double opt-in)
GET /v1/newsletter/confirm Confirm the weekly email subscription
GET /v1/unsubscribe/{token} Unsubscribe from an alert or the weekly email
POST /v1/unsubscribe/{token} Unsubscribe from an alert or the weekly email
POST /v1/reports Report a wrong price, a wrong match or a dead link
POST /v1/plan Subscription rotation plan: the smartest way to play a list of games
GET /v1/admin/queue Unresolved match queue with the raw record summary and the top 3 suggested games
POST /v1/admin/queue/{id}/resolve Resolve a queue row: approve the suggestion, reject the record or link it to a game
POST /v1/admin/queue/resolve-bulk Approve several queue rows for the same suggested game in one transaction (all or nothing)
GET /v1/admin/subscriptions/{code}/entries Subscription catalog entries with leaving dates
PUT /v1/admin/subscriptions/{code}/entries/{productId} Set the leaving date of an entry (from Xbox Wire); null clears it
GET /v1/admin/subscriptions/prices Every stored subscription monthly price with its source and the date it was checked
PUT /v1/admin/subscriptions/{code}/price Set the monthly price of a subscription in a region (no price source exists)
GET /v1/admin/keys API keys with 7-day usage (Redis and api_usage combined) and the 100+ requests flag
POST /v1/admin/keys/{id}/revoke Revoke an API key
GET /v1/admin/sources Source health: source_status plus the last fetched_at of each raw table; older than 24 hours is red
GET /v1/admin/games/{key}/offers Every offer of a game across sources and regions, plus its itad_id and ITAD historical-low seeds
GET /v1/admin/fee-rules Checkout fee rules per store, region and payment method
PUT /v1/admin/fee-rules/{id} Correct a fee rule (sets verified_at to now and status to verified)
GET /v1/admin/reports Wrong price / wrong match / dead link reports with weekly counts
POST /v1/admin/reports/{id}/resolve Mark a report as resolved
GET /v1/admin/kpi Experiment dashboard: weekly kpi_snapshot rows (newest first), the live current week and the targets
GET /v1/admin/api-metrics API and MCP usage: requests per key or anonymous, per endpoint or tool, error rate and latency percentiles
GET /v1/admin/experiment-report Weekly experiment report: KPIs, thresholds, funnel counts, clicks by channel, API usage, channel members, commission
POST /v1/admin/affiliate-sales/import Import an affiliate network sales report (CSV body, Content-Type: text/csv; up to 5 MB)
GET /v1/admin/affiliate-sales/summary Clicks to sales to commission, grouped by store, channel (src) and page

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/fairly-gg:fairly-api-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

fairly-gg-api-openapi.json Raw ↑
{
  "openapi": "3.1.1",
  "info": {
    "title": "Fairly API",
    "description": "Real prices for PC games, including checkout fees, and the cheapest way to play. Data is licensed CC BY 4.0, attribution: \"Fairly\"; game cover images are not covered by that license. Versioning and deprecation policy: https://fairly.gg/developers#versioning. Some store links are affiliate links; Fairly earns a commission on purchases and the price you pay does not change. Ranking is by price, never by commission. Append `?raw=1` to a `/go/{offer_id}` link for the plain store link.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.fairly.gg/"
    }
  ],
  "paths": {
    "/v1/games": {
      "get": {
        "tags": [
          "Games"
        ],
        "summary": "Search games by title",
        "description": "Title search (pg_trgm), first 20 results.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfGameSummary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/games/index": {
      "get": {
        "tags": [
          "Games"
        ],
        "summary": "Indexable games for the sitemap",
        "description": "Games with at least one active offer or active subscription entry, ordered by id. `id` is the URL id (`730` or `igdb:123`); `updated_at` is the latest offer fetch or subscription addition (sitemap `lastmod`). `page_size` defaults to 1000, max 5000.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-5000. Default 1000.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfGameIndexPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          }
        ]
      }
    },
    "/v1/games/{id}": {
      "get": {
        "tags": [
          "Games"
        ],
        "summary": "Game with offers, subscriptions, cheapest way to play and historical lows",
        "description": "`id` is a Steam AppID (`730`) or an IGDB id (`igdb:123`). `time_to_beat_minutes` is the main story length (IGDB time to beat, hastily); null when unknown, for free-to-play, multiplayer-only and live-service games, or outside 2-100 hours. `is_free_to_play` marks games that cost nothing to start: they have no price and are left out of price lists. SteamSpy playtime is never used as main story. `other_region_offers` lists offers measured in other regions for editions with at most one offer measured in the visitor region; they are labelled with `measured_in` and never enter `cheapest_way` or the ranking.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfGameDetailModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/games/{id}/history": {
      "get": {
        "tags": [
          "Games"
        ],
        "summary": "Authorized-store price history, daily minimum list price (before store fees)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfHistoryPoint"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/under/{amount}": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Games under $3, $5 or $10 (rounded to a round number in the requested currency)",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does. `min_review` defaults to 75 here.",
        "parameters": [
          {
            "name": "amount",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100; 0 turns the filter off. Default 75.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/tiers": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Under tiers ($3, $5, $10) as applied in the requested currency",
        "description": "`data` lists `usd` (3, 5 or 10) and `threshold` (Money: exactly the USD amount in USD; converted from USD and rounded to a round number in other currencies, e.g. TRY 100/200/400). The same amount filters /discover/under/{amount}. `euros` is a deprecated alias of `usd`.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfDiscoverTierModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/historical-lows": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Games at their historical low",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does.",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100. Default: no filter.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/leaving/{service}": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Subscription games leaving within 30 days (game-pass, ps-plus)",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does.",
        "parameters": [
          {
            "name": "service",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100. Default: no filter.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/play-with/{service}": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Games you can play with a subscription (game-pass, ps-plus)",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does.",
        "parameters": [
          {
            "name": "service",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100. Default: no filter.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/deal-of-the-day": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Deal of the day for the region",
        "description": "One game per region and UTC day, picked on the first request and stored (a game is not picked again for 7 days). Candidates: top-500 popularity, Steam reviews >= 85%, an authorized, fresh, discounted offer measured in the region, real price >= $2 (converted to the requested currency). Score is discount % x ln(1 + 500 / popularity rank). 404 when no game qualifies.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDealModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/popular": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "Most popular games with a usable offer",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does. Popularity order (Steam most played, wishlists, owners), free-to-play games excluded, only games with a fresh offer measured in the region. The list has a fixed short length (the home page chips); `page_size` can only shorten it.",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100. Default: no filter.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/discover/this-week": {
      "get": {
        "tags": [
          "Discover"
        ],
        "summary": "This week's picks",
        "description": "Parameters: `sort` (default, hours_per_dollar, discount, popularity, reviews), `page` (from 1), `page_size` (1-100, default 50), `min_review` (0-100; 0 turns the filter off). `data` holds `rows`, `total`, `at_low_count`, `best_value`, `page` and `page_size`. On under lists `default` and `hours_per_dollar` order by hours per dollar while any row has a time-to-beat, and by real price (ascending) when none does.",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "default, hours_per_dollar, discount, popularity or reviews.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number from 1 (at most 1000).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Rows per page, 1-100. Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_review",
            "in": "query",
            "description": "Minimum positive review percent, 0-100. Default: no filter.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "owned",
            "in": "query",
            "description": "Comma-separated subscription codes the caller already owns (e.g. `gp_pc,psplus_extra`). An owned subscription costs nothing, so a game in its catalog is \"already included\" instead of a buy recommendation. Nothing is stored. Unknown codes are rejected.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hide_owned",
            "in": "query",
            "description": "With `owned`: true leaves out games that are already included in an owned subscription. Default false.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfDiscoverPageModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/subscriptions": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Subscription summaries by region",
        "description": "Price per month in the region, catalog size (all games we list, the same number the site shows everywhere), new this week and leaving within 30 days.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfSubscriptionSummaryModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/subscriptions/{code}/catalog": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Subscription catalog by region",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfDealModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/stores": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "Stores with type, fee rules and verification dates",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfStoreView"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/meta/status": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Source health: last successful fetch, age, consecutive failures and whether it is overdue (no key needed)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseOfIReadOnlyListOfSourceStatus"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKey": [ ]
          },
          { }
        ]
      }
    },
    "/v1/keys": {
      "post": {
        "tags": [
          "Keys"
        ],
        "summary": "Create a developer API key",
        "description": "Returns the key once, in plain text. Only its SHA-256 hash is stored. Limited to 3 keys per IP per day.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateKeyRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedKeyModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/co

# --- truncated at 32 KB (109 KB total) ---
# Full source: https://artifacts.apis.io/fairly-gg/openapi/_original/fairly-gg-api-openapi.json