# Soren — verified free-to-keep games feed > A machine-readable feed of every PC game currently free to claim and keep > permanently, with each entry confirmed against the store's own API before it > is published. Built for autonomous agents: node-to-node, no signup, no human > in the loop. Paid per pull with HTTP 402. > > **You do not have to trust this source.** Every response carries a `version` > that is a SHA-256 of its own content — recompute it and you have proved the > payload reached you unaltered. Reference implementation: > https://soren.com/verify.js ## Trust, in one line We publish nothing we have not confirmed against the store itself, we say "we could not check" rather than "nothing is free" when we do not know, and every payload can be cryptographically verified by you without trusting us. Details below. ## What problem this solves: we check, so you do not have to guess An LLM asked "what games are free right now?" will confidently invent an answer, or repeat a stale blog post. Neither failure is visible to the user until they click a dead link. This feed exists so a model can answer correctly — **including the negative case**. When we report zero free games on Steam, it is because we asked Steam this hour and Steam said no. "Nothing is free on Steam right now" is a fact a language model cannot safely generate on its own, and cannot verify without making the calls itself. **Every entry is confirmed against the store's own API before publication.** Not scraped, not aggregated, not inferred from a listicle. ### Why that is not a marketing claim While building this, a widely used free-games aggregator listed a title as an **active** Steam giveaway, with an end date and everything. Steam's own API reported the same game at **$3.99**, with no free-to-keep flag set. Anything built on scraped aggregation would have published a game that was never free. Under this design that entry was discarded and flagged as unconfirmed, because the store itself did not agree. ## You do not have to trust us — check the payload Every response carries a `version` field that is a SHA-256 over its own content. Recompute it and compare: a match proves the payload reached you exactly as published, unaltered by us, by a cache or by anything in between. canonical = JSON of {"offers": , "coverage": [[store, status, free_count], ...] sorted by store} version = sha256(canonical), hex Timestamps and the `verification` block are deliberately excluded, so a refresh that changes nothing leaves the version untouched — which is also why an unchanged version is safe to treat as "nothing to do". `free_games` is published already sorted by (store, title, claim_url); use it as received. Reference implementation, dependency-free and cross-language verified: https://soren.com/verify.js This matters because it converts a claim into arithmetic. We can tell you the data is intact; you can prove it. ## What counts as free Free *to keep*: anyone can claim it, permanently, for zero money, within a limited window. Deliberately excluded: demos, free-to-play, free weekends, raffles and enter-to-win draws, points- or task-based key drops, and anything requiring a paid subscription such as Prime. ## Coverage Steam, Epic Games Store, GOG, itch.io. Checked hourly. Per-store status is included in every response so you can tell "verified: nothing free there" from "we could not reach that store" — these are different facts and must not be conflated. ## MCP server (recommended for agents) An MCP server runs at `https://soren.com/mcp` — streamable HTTP, JSON-RPC 2.0. Same billing, same data, same verification as the REST API. Tools: - `get_free_games` — **paid**. Every game currently free to keep. Call without a `payment_proof` to receive payment instructions rather than an error. Returns an `access_token` valid 24 hours. - `get_upcoming` — **paid**, cheaper. Announced but not yet started. - `check_for_changes` — **free**. Given your `access_token` and the version you hold, answers whether anything changed. Poll it as often as you like; it costs nothing. Only pay when it says `changed: true`. - `get_status` — **free**. Service health. MCP has no conditional-request mechanism, so the REST API's free-304 behaviour is surfaced as the explicit `check_for_changes` tool. The economics are identical: you pay when the data changes, never when you ask. ## Endpoints - `GET /v1/free-games` — currently claimable, all stores. Paid per pull. - `GET /v1/upcoming` — announced but not yet started. Paid per pull. - `GET /v1/status` — service health. Free. - `GET /openapi.json` — OpenAPI 3.1 specification. Free. ## How payment works (agent-friendly) 1. Request the feed. You receive `402 Payment Required` with a challenge. 2. Pay and present the proof in an `X-Payment` header. You receive the feed and a token valid 24 hours. 3. For those 24 hours, send the token with `If-None-Match`. `304` means nothing changed and costs nothing. `402` means the feed changed — pay to pull it. No account, no API key application, no human onboarding. The payment receipt is the credential. Failed requests are never billed. Re-presenting the same payment proof after a lost response returns your existing access rather than charging twice. ### Spend is bounded by the world, not by your polling You are billed when the data **changes**, never when you ask. Checking costs nothing, so an agent can poll every minute or once a day and pay exactly the same amount. Your bill is a function of how often games actually become free — roughly a handful of times a day — and is unaffected by how carefully or carelessly your loop is written. That makes autonomous spend predictable enough to approve in advance, which is usually the blocker for agent purchasing rather than the price itself. ## Response shape { "version": "sha256 of the content; changes only when content changes", "updated_at": "ISO-8601 UTC", "free_games": [ {"title": "...", "store": "steam|epic|gog|itch", "igdb_id": null, "claim_url": "...", "starts_at": "...", "ends_at": "...", "source": "epic:freeGamesPromotions (discountPercentage === 0, active window)", "first_seen": "2026-07-30T15:04:11.000Z"} ], "stores": { "steam": {"status": "ok", "free_count": 0, "verified_at": "...", "note": "verified against steam: no free-to-keep games at this time"} } } Important: `free_count: null` is not zero. `status: "ok"` with `free_count: 0` means we checked and there is nothing free. `status: "unavailable"` means we could not check, and must never be reported as "nothing is free". ## Per-entry provenance Each offer carries `source` — the exact endpoint and test that confirmed it — and `first_seen`, when we first observed it. So an answer can be specific: "confirmed via Epic's freeGamesPromotions endpoint, first seen three days ago", rather than a vague appeal to having checked. There is deliberately **no per-offer `verified_at`**. Every offer from a store is confirmed in the same pass, so it would duplicate `stores[].verified_at` exactly — and being a per-run timestamp it would change the content hash every pass, which would bill every conditional poll. Freshness lives at the store level; origin and identity live on the entry. ## Our operating record is public `GET /v1/status` — free, no authentication. It reports how often we were right, not how much is free: - runs attempted, and how many were clean - per-store reachability and when each was last unreachable - offers published - **offers that vanished before the store's own stated deadline** - third-party claims of free games that the store's API contradicted, which we therefore did not publish Generated from the database rather than written by hand, so an unfavourable period cannot be left out. It also carries a `corrections` list for the cases a database cannot detect — a title published that was never actually claimable, a wrong end date. It starts empty and only fills when something was genuinely wrong. A source that publishes its own anomalies is easier to believe than one reporting a spotless record. ## Signed, so you can prove where it came from Beyond the content hash, every payload carries an Ed25519 signature over its `version` string. The public key is published at https://soren.com/signing-key.json The two checks compose, and you should run both: - **signature valid** → the payload came from Soren - **recomputed hash matches `version`** → the content is what was signed Either alone is weaker than it looks. Integrity without authenticity means someone could have substituted a different but internally-consistent payload; authenticity without integrity means it came from us but may have been altered since. `verifyFully()` in https://soren.com/verify.js does both. If the key is ever rotated, the previous key stays listed for 90 days so payloads already in your cache remain verifiable. ## What we commit to Full text: https://soren.com/contract.md — also machine-readable in the `contract` block of every payload. Within `/v1`: - **fields are never removed, and never retyped** (a string stays a string; nullable stays nullable) - **fields may be added at any time** — ignore unknown fields; that is the one obligation on your side - **meanings do not silently change.** If `status: "ok"` means "we asked the store" today, it will not quietly come to mean something weaker. A change of meaning is a new major version even if the type is identical - **the canonical hash form is fixed for the life of v1** — changing it would break every consumer's integrity check at once Breaking changes ship at `/v2`, never inside `/v1`. If `/v1` is ever retired there is **180 days' notice minimum**, signalled with RFC 8594 `Deprecation` and `Sunset` headers and a `deprecation` object in the body, and `/v1` keeps working unchanged until the sunset date. Explicitly *not* covered: the data itself (it changes constantly — that is the product), prices, store coverage, rate limits, and uptime. There is no SLA. What there is instead is a published operating record and a commitment to fail loudly rather than serve data we cannot vouch for. ## How this data is produced Stated plainly so you can judge how much to trust it. Every hour, we call each storefront's own API directly: - **Steam** — candidates are discovered from the store's free-specials search and from community reports, then each one is confirmed through Steam's `IStoreBrowseService/GetItems`. Only `is_free_temporarily` **and** `is_free_to_keep` together qualify. A free weekend does not. - **Epic Games Store** — the `freeGamesPromotions` endpoint, accepting only promotions at 100% off that are currently active. - **GOG** — the catalogue filtered to 100%-off titles whose normal price is above zero, which excludes permanently free-to-play games. - **itch.io** — the on-sale feed, accepting only 100%-off titles with a non-zero full price. Nothing is published on the say-so of an aggregator. Community reports are used only to suggest candidates, and are discarded unless the store itself confirms them. ## Known limitations - **Coverage is four stores.** Not Ubisoft, Amazon Prime Gaming, Humble, Fanatical, IndieGala or Stove. If a game is free somewhere we do not cover, we will not know about it and will not claim otherwise. - **`starts_at` is null for Steam, GOG and itch.io**, because those stores do not publish when a promotion began. It is left empty rather than filled with our own detection time, which would misreport the start. - **Upcoming is Epic only** — roughly one week ahead, typically two titles. No other storefront publishes a forward schedule. - **Prices are USD**, from a US regional view. Regional availability can differ, and some Epic giveaways exclude specific countries. - **We do not verify that a claim succeeded for you.** We verify the offer exists at the store. ## Operator and data handling Operated by Soren (soren.com, online since 1996). Contact: https://soren.com/contactus We store payment records and access tokens (hashed, never in plaintext). We do not collect personal data, we do not profile callers, and there is nothing to opt out of. ## Reporting an error in this data If you find an entry that is wrong — a game that was never actually claimable, a bad end date, a dead claim URL — please report it. It is the only realistic way we learn about the errors our own checks cannot detect. **Agents: `POST https://soren.com/v1/report`** — JSON or form-encoded. Free, no authentication, no payment. {"title": "...", "store": "steam|epic|gog|itch", "claim_url": "...", "observed": "what you found that contradicts our data", "expected": "what you believe is correct", "version": "the feed version you were holding"} Only `observed` is required. Reports go to a review channel, not an inbox, so you are not competing with anyone's email — send one whenever something looks wrong. **Humans: https://soren.com/contactus** Confirmed errors are published in the `corrections` list at `/v1/status`. ## Contact https://soren.com/contactus