Soren HTTP 402 · agent-native

We check. So your agent doesn’t guess.

Every free-to-keep PC game, confirmed against the store’s own API before it ships. One call instead of six, one shape instead of four — and a negative you can trust: when we say nothing is free on Steam right now, it’s because we asked Steam this hour.

Verified, not scraped

Confirmed against the store's own API before publication. Not aggregated, not inferred from a listicle.

Trustworthy negatives

Per-store status, so an agent can say "nothing is free there" as fact instead of guessing.

Free change-detection

Pay per pull. Between pulls, conditional checks are free for 24 hours — poll as often as you like.

Node-to-node by design

No signup, no API-key application, no human in the loop. The payment receipt is the credential — an agent can discover, pay and consume this entirely on its own.

Never billed for failure

4xx and 5xx are always free, and a lost response never double-charges: re-present the same payment proof and get your access back.

Built for machines

This is an N2N service: designed for autonomous agents, MCP servers and machine-to-machine pipelines rather than people clicking buttons. Discovery is machine-readable (llms.txt, OpenAPI 3.1), payment is HTTP 402 with no onboarding, and the response carries provenance — which store confirmed each entry and when — so a model can cite it instead of guessing.

Why "verified" isn't marketing copy

While building this, a widely used free-games aggregator listed a title as an active Steam giveaway — with an end date. Steam’s own API reported the same game at $3.99, with no free-to-keep flag set.

Anything built on scraped aggregation publishes that game as free. Here it was discarded, because the store itself disagreed. That is the entire product: “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.

Spend bounded by the world, not your polling

You are billed when the data changes, never when you ask. Checking costs nothing, so an agent polling every minute and one polling once a day pay exactly the same. Your bill is a function of how often games actually become free — a handful of times a day — not of how carefully your loop is written.

That makes autonomous spend predictable enough to approve in advance, which is usually the real blocker for agent purchasing rather than the price.

Quick start

# 1. Ask. You'll get a 402 with a payment challenge.
curl -i https://soren.com/v1/free-games

# 2. Pay, and present the proof. You get the feed and a 24h token.
curl -i https://soren.com/v1/free-games \
  -H "X-Payment: <proof>"

# 3. For the next 24h, check for changes for free.
#    304 = nothing new. 402 = something changed, pay to pull it.
curl -i https://soren.com/v1/free-games \
  -H "Authorization: Bearer <token>" \
  -H 'If-None-Match: "<version>"'

Response

{
  "version": "733d91c2…",           // changes only when content does
  "updated_at": "2026-07-31T15:17:52Z",
  "free_games": [
    { "title": "OTXO", "store": "epic", "igdb_id": null,
      "claim_url": "https://store.epicgames.com/en-US/p/otxo-396b8b",
      "starts_at": "2026-07-30T15:00:00Z",
      "ends_at": "2026-08-06T15:00:00Z" }
  ],
  "stores": {
    "steam": { "status": "ok", "free_count": 0,
                "note": "verified against steam: no free-to-keep games at this time" },
    "gog":   { "status": "unavailable", "free_count": null,
                "note": "could not be reached — NOT a statement that nothing is free" }
  }
}
free_count: null is not zero. ok with 0 means we asked and there is nothing. unavailable means we could not check. Rendering the second as "nothing is free" is the exact false negative this feed exists to prevent.

Endpoints

EndpointWhatPrice
GET /v1/free-gamesCurrently claimable, all stores$0.02 / pull
GET /v1/upcomingAnnounced but not started yet$0.01 / pull
POST /mcpMCP server — same data, same billingsee below
GET /v1/statusService healthfree
GET /openapi.jsonOpenAPI 3.1 specfree
GET /llms.txtWhat this is, for agentsfree

Every entry says where it came from

Each game carries the exact endpoint and test that confirmed it, plus when we first saw it — so an answer can be specific rather than a general appeal to having checked.

{
  "title": "OTXO",
  "claim_url": "https://store.epicgames.com/en-US/p/otxo-396b8b",
  "source": "epic:freeGamesPromotions (discountPercentage === 0, active window)",
  "first_seen": "2026-07-31T00:20:01.987Z"
}

There is deliberately no per-game verified_at: every entry from a store is confirmed in the same pass, so it would duplicate the store-level timestamp — and being a per-run value inside the hashed content, it would change the version every pass and bill every check. Freshness lives at the store level; origin lives on the entry.

Don’t trust us — verify us

Every response carries a version that is a SHA-256 of its own content. Recompute it and a match proves the payload reached you exactly as published — unaltered by us, by a cache, or by anything in between.

// dependency-free, works in Node, Deno, Bun and browsers
import { verifyFeed } from 'https://soren.com/verify.js';
const feed = await (await fetch('https://soren.com/v1/free-games', { headers })).json();
console.log(await verifyFeed(feed));   // { valid: true, ... }

Timestamps are excluded from the hash, so a refresh that changes nothing leaves the version untouched. That is the same property your billing rests on: an unchanged version is genuinely unchanged data.

MCP server

Point your agent at https://soren.com/mcp — streamable HTTP, JSON-RPC 2.0, no separate signup. Four tools: get_free_games and get_upcoming (paid), check_for_changes and get_status (free).

Calling a paid tool without a payment proof returns payment instructions, not an error — so an agent can discover the price and pay without a human ever being involved.

Status codes

CodeMeaningBilled
200Feed returned, fresh 24h token issuedyes
304Nothing changed since your versionno
402Payment needed, or the feed changedno
429Rate limited — wait Retry-After secondsno
503Feed stale or unpublished — we fail rather than serve itno

Signed, and contractually stable

Every payload carries an Ed25519 signature over its version string, with the public key at /signing-key.json. The signature proves it came from us; recomputing the hash proves the content is what was signed. verifyFully() does both.

And the shape holds still. Within /v1: fields are never removed or retyped, meanings never silently change, and the canonical hash form is fixed. Additions can appear without notice — ignore unknown fields. Breaking changes ship at /v2 with 180 days’ notice. Full contract.

Our operating record, published

/v1/status is free and unauthenticated, and reports how often we were right — not how much is free. Runs attempted and how many were clean, per-store reachability, offers published, and offers that vanished before the store’s own stated deadline.

It is generated from the database, not written by hand, so a bad week cannot be quietly left out. A source that publishes its own anomalies is easier to believe than one reporting a spotless record.

Coverage

Steam · Epic · GOG · itch.io — checked hourly. Free-to-keep only: no demos, no free-to-play, no free weekends, no raffles, no key drops you have to earn.