API and MCP access are included with a Business account.
One seat included, scoped keys for every member, and a shared monthly quota.
Business API v1
The Business API gives programmatic access to the same market intelligence Game Oracle renders in the app: catalogue search, game reports, Game Gap market segments, outlier leaderboards and Concept Compass concepts. It is available to every member of an active Business account.
Base URL: https://www.game-oracle.com/api/business/v1
Every endpoint returns JSON. Bodies are JSON unless noted (image search also accepts multipart uploads).
Authentication
Pass your API key in the Authorization header on every request:
Authorization: Bearer go_biz_<key> Keys are created and revoked from Account Settings. Each key is scoped (see below) and belongs to the member who created it; every member of a Business account may hold up to five active keys.
Scopes
| Scope | Grants |
|---|---|
search:read | All /search/* endpoints |
games:read | /games/{app_id}, /games/reports and the jobs they create |
gaps:read | /game-gap/* endpoints and the jobs they create |
outliers:read | /outliers |
concepts:write | All /concept-compass/* endpoints and the jobs they create |
Job endpoints (/jobs/*) require the scope of the job’s type. A key missing a scope receives:
{
"success": false,
"error": "Key missing required scope: games:read",
"code": "INSUFFICIENT_SCOPE"
} Rate Limits & Quotas
| Control | Default |
|---|---|
| Per-minute rate limit (per key) | 60 requests/minute |
| Monthly quota (per Business account) | 10,000 units/month |
The monthly quota is shared by every key on the account. Most requests cost one unit. The exceptions:
- Batch report jobs cost one unit per id at submission (ids that do not exist are refunded). A cancelled job refunds its unprocessed items.
- Concept Compass costs 8 units to create a concept and 3 units to update its competitors.
- Polling and reference data (
GET /jobs/{id},GET /jobs/{id}/results,GET /search/fields, everyGET /reference/*) are free — rate-limited but never consuming quota.
A request that would push the account over its quota is refused with 429 QUOTA_EXCEEDED before any work is done.
Staying under the rate limit
The per-minute limit is per key, and every request counts towards it — including free ones and, over MCP, the connection handshake. Two habits keep an integration clear of 429 RATE_LIMITED:
- Pace on the headers. Every response carries
X-RateLimit-RemainingandX-RateLimit-Reset(Unix seconds). WhenRemainingreaches one or two, wait untilResetbefore the next call rather than retrying on a 429. ARetry-Afterheader accompanies every 429. - Rotate keys for parallel work. Each member of the account can hold up to five keys and every key has its own window, while the monthly quota is shared. Give each worker, agent or environment its own key (from Account Settings) instead of fanning one key out across them.
Batch endpoints exist for the same reason: one POST /games/reports with 100 ids is one request against the window, where 100 GET /games/{app_id} calls would be a hundred.
Response headers
| Header | Description |
|---|---|
X-RateLimit-Limit | Per-minute cap for this key |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-Quota-Limit | The account’s monthly quota |
X-Quota-Used | Units consumed this month across all keys |
Error Model
{
"success": false,
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE",
"details": [{ "path": "appIds", "message": "Too big: expected array to have <=100 items" }]
} details is present on validation errors only.
| Status | Codes |
|---|---|
| 400 | VALIDATION_ERROR |
| 401 | INVALID_KEY |
| 403 | INSUFFICIENT_SCOPE, SUBSCRIPTION_REQUIRED, SEAT_REVOKED |
| 404 | NOT_FOUND |
| 409 | JOB_NOT_FINISHED, JOB_NOT_ACTIVE, JOB_IN_PROGRESS |
| 429 | RATE_LIMITED, QUOTA_EXCEEDED, TOO_MANY_ACTIVE_JOBS |
| 500 | SERVER_ERROR |
| 502 | UPSTREAM_ERROR |
| 503 | SERVICE_UNAVAILABLE, CONCURRENCY_LIMIT (retry after the Retry-After header) |
Search
Five endpoints share one result format. All are POST with a JSON body.
| Endpoint | Ranks by |
|---|---|
/search/title | Similarity to an existing game (appId) |
/search/description | Similarity to a free-text description (query) |
/search/image | Visual similarity to one or more images |
/search/attributes | No ranking — structured filters only, sortable |
/search/combined | Rank fusion of any mix of title, description and images |
Common body fields
| Field | Type | Default | Notes |
|---|---|---|---|
nNeighbours | integer 1–10000 | 1000 | Semantic searches only: how many closest games to consider |
page | integer ≥ 1 | 1 | |
pageSize | integer 1–100 | 25 | |
filters | object | — | The Data Explorer advanced filters (below) |
attributes | object | — | Free-form filters over any catalogue field (below) |
sort | { field, direction } | app_id asc | Attribute search only; semantic searches are always ordered by distance |
filters — Data Explorer advanced filters
Identical to the controls in the Data Explorer. Lists accept arrays or comma-separated strings; ranges accept [min, max] or "min,max".
| Field | Type / values |
|---|---|
includeTags, excludeTags | string[] |
releasedOrUnreleased | Released | Unreleased | Both |
earlyAccess, aiDisclosure | Include | Exclude | Only |
pricingType | Free | Paid | Both |
devType | Self-Published | AAA | All |
minSteamDBScore, minPercPositiveReviews | number 0–100 |
minReviewCount, minPeerRank, minUniquenessScore | number ≥ 0 |
excludeNSFW | boolean |
releaseDateStart, releaseDateEnd | YYYY-MM-DD |
wishlistRange, unitsSoldRange, revenueRange, devLifetimeRevenueRange, pubLifetimeRevenueRange, initialPriceRange, latestPriceRange | [min, max] |
gameEngine | string (substring match) |
attributes — any catalogue field
attributes is a map of field name → condition, where a condition is a bare value (equality), an array ($in), or an operator object:
{
"attributes": {
"is_free": false,
"meta_tags": ["Roguelike", "Deckbuilder"],
"scores.uniqueness_score": { "$gte": 6.5 },
"store_release_date.year": { "$in": [2025, 2026] },
"title": { "$regex": "farm", "$options": "i" }
}
} Operators: $eq $ne $gt $gte $lt $lte $in $nin $exists $all $regex. { "$eq": null } matches fields with no value and { "$ne": null } fields that have one. $regex is a case-insensitive substring match and is only accepted on text fields; $all requires every listed value (tags, languages). Up to 40 fields per request.
GET /search/fields returns every filterable field with its accepted operators and whether it is sortable. Fields include every column of the games table (app_id, title, initial_price, latest_price, is_free, early_access, coming_soon, age_days, game_engine, ai_disclosure, …), the satellite groups (review_count.*, review_state.*, scores.*, estimated_units_sold.*, estimated_revenue.*, estimated_wishlist.*, estimated_wishlist_pre_launch.*, estimated_wishlist_post_launch.*, developers_lifetime_revenue.*, store_release_date.*, latest_release_date.*, age_normalised_rank.*, pc_min_requirements.*, time_to_complete.*, news.*, followers) and the relations (meta_tags, developers, publishers, supported_languages, platform_windows, platform_linux, image_urls).
When filters and attributes constrain the same field, their operators are merged and attributes wins on conflicts.
Reference data — the values you can search for
Guessing tag names or engine strings is the usual way to get an empty result. These endpoints return the live vocabularies so a client (or an agent) can build a valid query first. All of them are free of quota and need any active key, whatever its scopes.
| Endpoint | Returns |
|---|---|
GET /search/fields | Every filterable/sortable field with its operators, plus filters — the enumerated options of each filters key — and reference links to the endpoints below |
GET /reference/tags | Every Steam tag the catalogue uses: all_tags, genres and themes (grouped { name, tags[] }), visuals, player_support, nsfw, steam_features, for attributes.meta_tags and filters.includeTags |
GET /reference/values/{field} | Distinct values of one low-cardinality field, most common first ({ value, games }): game_engine, age_category, publication_era, controller_support, supported_languages |
GET /reference/segments | The vocabulary of GET /game-gap/segments: dimensionalities, the live subcategory list per dimensionality, sortBy keys, exclusion flags, range parameters |
GET /reference/concept-options | The tag lists, enums, defaults and limits POST /concept-compass/concepts validates against |
GET /reference/features | Search the ~470 game design features (“building blocks”) by name or definition (q, category, offset; 50 per page). Their ids are what featureIds accepts |
GET /reference/values/game_engine
→ { "field": "game_engine", "attribute": "game_engine",
"values": [{ "value": "Unity", "games": 28711 }, { "value": "Unreal Engine", "games": 9147 }, …] } Values are returned exactly as stored so they match in attributes (some supported_languages entries carry a leading space, for example — use the strings as given). Lists are cached server-side for ten minutes.
Title search
POST /search/title
{ "appId": 413150, "nNeighbours": 500, "filters": { "pricingType": "Paid" }, "pageSize": 50 } The queried game is always first with distance: 0. 404 NOT_FOUND if the app id is unknown.
Description search
POST /search/description
{ "query": "A cozy farming sim where you rebuild a lighthouse town", "nNeighbours": 1000 } Image search
JSON form — public https:// image URLs and/or inline base64:
POST /search/image
{
"imageUrls": ["https://example.com/capsule.png"],
"images": [{ "data": "<base64>", "mimeType": "image/png" }],
"nNeighbours": 1000
} Multipart form — files in images (repeat the field), with optional string fields nNeighbours, page, pageSize and JSON-encoded filters, attributes, sort:
curl -X POST https://www.game-oracle.com/api/business/v1/search/image
-H "Authorization: Bearer go_biz_…"
-F "images=@screenshot1.png" -F "images=@screenshot2.png"
-F "nNeighbours=500" -F 'filters={"excludeNSFW":true}' Up to 5 images, 10 MB each. Image URLs must be public https:// addresses. This search can take up to a few minutes when the image service is cold; the request waits.
Attribute search
POST /search/attributes
{
"attributes": { "coming_soon": true, "estimated_wishlist.mid": { "$gte": 20000 } },
"filters": { "excludeNSFW": true },
"sort": { "field": "estimated_wishlist.mid", "direction": "desc" },
"pageSize": 100
} Combined search
POST /search/combined
{
"title": { "appId": 413150 },
"description": { "query": "farming sim with a mystery" },
"image": { "imageUrls": ["https://example.com/capsule.png"] },
"weights": { "title": 0.5, "description": 0.3, "image": 0.2 },
"nNeighbours": 1000,
"filters": { "releasedOrUnreleased": "Released" }
} Each source is queried in parallel and the rankings fused (reciprocal rank fusion, the same as the Data Explorer). weights overrides the default split. With more than one source, distance is a normalised rank score (0 = top), not a cosine distance.
Result format
{
"success": true,
"data": [
{
"app_id": 413150,
"title": "Stardew Valley",
"…": "every games-table field plus review_count, review_state, scores, estimated_* ranges, release dates, meta_tags, developers, publishers, platforms, supported_languages, …",
"socials": { "app_id": 413150, "twitter": "…", "discord": "…" },
"distance": 0,
"source": "title"
}
],
"meta": { "page": 1, "pageSize": 25, "total": 812, "candidates": 1000 }
} total is the number of games matching your filters (within the nNeighbours candidates for semantic searches); candidates is how many the semantic source returned before filtering.
Game reports
GET /games/{app_id}
The full report the /games/{app_id} page shows:
| Field | Contents |
|---|---|
game | Every catalogue field for the game |
socials | Store and social links |
tags | The game’s tags grouped: genres, themes, visuals, playerSupport, steamFeatures, other |
benchmarks | Steam-wide baselines every stat is compared against |
segments | The market segments the game belongs to (with displayTitle) |
playerHistory | Monthly average and peak concurrent players |
newsTimeline | { dates }: up to 50 Steam news publication dates, newest first; null if never collected |
relationships | DLC, demos, purchase options and packages from the store page, grouped (see Store relationships) |
reviewSummary | Review analysis: overall, positives[], negatives[] |
similarGames | The 100 most similar games, closest first, each with distance |
generation | Whether reviewSummary was served from cache or produced on this request |
Review analysis is produced on first request and cached for later ones, so a report may take a few seconds the first time.
Wishlist fields
Every game row carries three wishlist ranges (mid, lower, upper):
| Field | Meaning |
|---|---|
estimated_wishlist | All-time wishlists |
estimated_wishlist_pre_launch | Wishlists the game had when it launched (first day on sale) |
estimated_wishlist_post_launch | Wishlists added since launch |
Pre-launch plus post-launch equals all-time, bound by bound. For unreleased games pre-launch equals all-time and post-launch is 0. A null split means “not available”, usually because the game launched before follower tracking began. All three are filterable and sortable (for example "sort": { "field": "estimated_wishlist_pre_launch.mid", "direction": "desc" }).
benchmarks adds wishlist_estimate_pre_launch and wishlist_estimate_post_launch: medians across released games that have the split, beside the all-time wishlist_estimate.
wishlist_estimate_before_release is deprecated. It is still returned, but estimated_wishlist_pre_launch.mid replaces it.
POST /games/reports — batch
POST /games/reports
{ "appIds": [413150, 1145360, 1794680] } Up to 100 ids. Returns 202 Accepted with a job (see Jobs). Costs one unit per id; unknown ids are marked skipped and refunded.
Game Gap
GET /game-gap/segments
Search market segments with the same filters as the Game Gap page. All parameters are query-string values.
| Parameter | Values |
|---|---|
dimensionality | Both | 2D | 3D |
subcategory | A subcategory name, or __NULL__ for top-level segments |
keyword | Substring match on the segment’s keywords |
sortBy | demand_score | avg_uniqueness_score | median_estimated_steam_sales (default: top picks) |
excludeGoliaths, excludeStampede, excludeAchievementFarming, excludeCultFollowing, excludeNsfw (or excludeNSFW), excludeFranchiseDominated | true to exclude that segment type |
minPercComingSoon/maxPercComingSoon, minPercIndie/maxPercIndie, minPercVpos/maxPercVpos | fractions 0–1 |
minMedianSales/maxMedianSales, minMedianWishlists/maxMedianWishlists, minMedianRevenue/maxMedianRevenue, minMedianDevLifetimeRevenue/maxMedianDevLifetimeRevenue, minMedianUniquenessScore/maxMedianUniquenessScore, minMedianAge/maxMedianAge | numbers |
clusterIds | Comma-separated ids (≤100) — fetch specific segments instead of filtering |
page, pageSize | pageSize ≤ 50, default 20 |
GET /reference/segments lists the live subcategories and every accepted parameter. Each row is the segment list item (cluster_id, title, summary, keywords, total, demand_score, avg_uniqueness_score, median_estimated_steam_sales, median_wishlist_estimate, warning flags, …) plus displayTitle. Segments that have never been titled get one generated for the first ten on the page; the rest use a keyword-derived displayTitle until a later request fills them in.
GET /game-gap/segments/{cluster_id}
The full report the /game-gap/{cluster_id} page shows:
| Field | Contents |
|---|---|
segment | The segment row: composition percentages, medians, trend figures, warning flags, app_ids |
title | Display title |
games | Every game in the segment (full catalogue rows) |
overview | detailed_summary, five_similar_features, market_gaps, biggest_opportunities, biggest_challenges, developer_fit |
sentiment | Player sentiment: overview, positives[], negatives[] |
benchmarks | Steam-wide baselines |
metrics | Derived exactly as the page derives them: warnings, badges (missed opportunity / underserved audience), performance medians vs Steam (wishlists as estimatedWishlist, all-time, and estimatedWishlistPreLaunch, both across released and unreleased games), launchEfficiency, hollowHitAppIds, topOutlierAppIds, gameEngines |
generation | Whether overview / sentiment came from cache or were produced on this request |
Overview and sentiment are produced on first request and cached for 30 days.
POST /game-gap/reports — batch
POST /game-gap/reports
{ "clusterIds": ["v2-both-cluster-3", "v2-2d-cluster-17"] } Up to 100 ids; 202 Accepted with a job. One unit per id.
Outliers
GET /outliers?listType=upcoming_games|released_games
The /leaderboards/outliers lists: the top 100 games by uniqueness, ranked by estimated wishlists (upcoming_games) or SteamDB score (released_games). Rows: rank, app_id, title, steam_db_score, uniqueness_score, estimated_units_sold, estimated_units_sold_steam, wishlist_estimate, capsule_img_url, coming_soon, free_to_play.
Concept Compass
Concepts were called projects. Concept Compass now calls its saved ideas concepts. The original
/concept-compass/projectspaths, theprojectIdfield and thecreate_concept_projectMCP tool keep working as aliases, so existing integrations need no changes.
POST /concept-compass/concepts
Create a concept from the same inputs the Concept Compass wizard collects. The concept appears in your Concept Compass concepts list immediately and is filled in as the job runs. POST /concept-compass/projects is an alias.
POST /concept-compass/concepts
{
"name": "Lighthouse Keeper",
"engine": "Godot",
"teamSize": 2,
"scope": "Small",
"shortDescription": "A cozy farming sim where you rebuild a lighthouse town and uncover its past.",
"visualStyle": "2D",
"artStyle": ["Pixel Graphics"],
"playerPerspective": ["Top-Down"],
"themesGenres": ["Farming Sim", "Cozy", "Mystery"],
"aesthetics": "Warm palette, soft synth soundtrack",
"gameplay": "Daily loop of farming, fishing and restoring buildings",
"narrative": "Letters from the previous keeper reveal why the town emptied",
"playerExperience": "Calm with a slow-burn mystery",
"featureIds": ["f_a7c36776d58e41d4"]
} name and shortDescription (≥10 characters) are required; visualStyle, artStyle, playerPerspective and themesGenres accept the wizard’s tag lists; everything else defaults as in the wizard. Reference images are not accepted over the API.
featureIds (optional) names 1–5 core features — the design pillars the concept is built around — using ids from GET /reference/features. They sharpen the competitor search (games verified to share them are considered first) and are woven into every generated section. An unknown id is a 400 listing details.unknownIds, and nothing is created or charged.
Returns 202 Accepted:
{
"success": true,
"data": {
"jobId": "…",
"type": "concept_create",
"status": "queued",
"stage": "landscape",
"conceptId": "…",
"projectId": "…",
"pollUrl": "/api/business/v1/jobs/…",
"reportUrl": "/api/business/v1/concept-compass/concepts/…",
"items": [
{ "id": "landscape", "status": "pending" },
{ "id": "resources", "status": "pending" },
"…"
]
}
} projectId repeats conceptId for older integrations. Costs 8 units. The job runs the competitor landscape first (retrieval, relevance evaluation), then the report sections in parallel; a section that fails is left empty and reported in items.
GET /concept-compass/concepts/{conceptId}
GET /concept-compass/projects/{projectId} is an alias. While a job is writing to the concept: 202 with { "status": "generating", "job": { … } } and a Retry-After header. Otherwise 200 with the full report:
| Field | Contents |
|---|---|
input | The wizard fields |
competitors[] | Each competitor’s full catalogue row plus distance, relevance (Very/Somewhat), relevanceReason, discoveryMethod |
headline | uniquenessScore, sales, reviews, revenue, wishlists (unreleased competitors), wishlistsPreLaunch, wishlistsPostLaunch, wishlistsAllTime, wishlistConversion (mid/low/high), priceRange, counts, validPublisherIds |
projection | Per-outcome forecasts (reviews, units, revenue, wishlists) with distribution quantiles, evidence quality and the evidence accounting behind each; completion is the expected gameplay length (band, confidence, range, known/missing, agreement, factors), or null without competitor bands |
boxleiterMultipliers | The review-to-sales multiplier range used for the cross-check |
resources | suggestions (technical components, visuals, audio, level design, hard problems, search/asset keywords) and suggestedPublishers |
marketing | suggestedSteamDescription, ideas[], suggestedTags |
competition | summary, playerSentiment |
gdd | draft, markdown, generatedAt, editedAt, inspirationCompetitorIds. Filled only for older concepts that have a Draft GDD; each is null for new ones |
generation | generatedFromCompetitorIds and drift (how far the competitor list has moved since) |
POST /concept-compass/update-competitors/{conceptId}
POST /concept-compass/update-competitors/{conceptId}
{ "competitorIds": [413150, 1145360, 1794680] } Replaces the competitor list (up to 100 ids; unknown ids are dropped). Added games have their similarity measured against your concept; removed games are dropped. The competition summary and player sentiment are regenerated; projections and headline statistics are derived on read and update automatically. Returns 202 with a job; 409 JOB_IN_PROGRESS if the concept already has a running job. Costs 3 units.
Concepts and Workspace projects
New concepts don’t include a draft game design document, so their gdd fields are null. In the app, a subscriber can choose Add to Project on a completed concept to turn it into a Workspace project with an editable GDD, boards and a Competitor Radar. That happens in the app only: it doesn’t change what these endpoints return and uses no API quota. Updating a concept’s competitors through the API doesn’t change a project it’s linked to; the owner chooses Update in Project in the app when they want the project refreshed.
Jobs
Batch reports and Concept Compass generation run as jobs. A job passes through queued → running → one of completed, partial (some items failed), failed or cancelled. Work starts within seconds of submission; a full batch of 100 reports typically finishes in a few minutes. Jobs and their results are retained for 7 days.
At most 5 jobs per account may be queued or running at once (429 TOO_MANY_ACTIVE_JOBS).
GET /jobs/{jobId} — poll (free)
{
"success": true,
"data": {
"jobId": "…",
"type": "segment_reports",
"status": "running",
"progress": { "total": 40, "done": 12, "failed": 0, "skipped": 1 },
"items": [{ "id": "v2-both-cluster-3", "status": "done" }, "…"],
"createdAt": "…",
"updatedAt": "…",
"completedAt": null,
"expiresAt": "…",
"resultsUrl": "/api/business/v1/jobs/…/results"
}
} Poll every few seconds; polling never consumes quota.
GET /jobs/{jobId}/results?page&pageSize — results (free)
Available once the job is terminal (409 JOB_NOT_FINISHED before that). Each entry carries the item’s status and, for done items, the full report — the same object GET /games/{app_id} or GET /game-gap/segments/{cluster_id} returns. pageSize is at most 25 because each entry is a full report.
{
"success": true,
"data": [
{
"id": "413150",
"status": "done",
"error": null,
"report": { "game": { "…": "…" }, "reviewSummary": { "…": "…" } }
},
{ "id": "999999", "status": "skipped", "error": "Not found", "report": null }
],
"meta": { "page": 1, "pageSize": 10, "total": 3, "jobStatus": "completed" }
} For Concept Compass jobs the results endpoint returns the job and its reportUrl; fetch the report from there.
DELETE /jobs/{jobId} — cancel
Cancels a queued or running job. Items not yet processed are marked skipped and their units refunded (refundedUnits in the response).
MCP
The same operations are available over the Model Context Protocol at POST /api/business/v1/mcp, authenticated with the same key. MCP tools additionally accept compact: true and fields: [...] to trim their output for an agent’s context window. See the MCP server guide for client setup, the tool list and the output-shaping options.
Time to complete
Catalogue games include time_to_complete, also available in game, similarGames, segment games, and concept competitor game records. It is null if no estimate is available.
{
"time_to_complete": {
"time_to_complete_band": "10-20h",
"confidence": "high",
"warnings": [],
"has_ending": true,
"source": "igdb",
"igdb_time_to_complete_source": "normally",
"igdb_submission_count": 36
}
} The stored band uses IGDB’s reported time when available, otherwise Game Oracle’s prediction. Bands are ordered <2h, 2-5h, 5-10h, 10-20h, 20-50h, 50h+. confidence is high, medium or low and describes the prediction model, not the reliability of an IGDB report. has_ending: false indicates ongoing play without a clear finish line.
IGDB source is normally, from_hastily or from_completely; the latter two are normal-pace estimates derived from the reported playstyle. IGDB source/count are nullable for prediction-only games. Raw predicted hours and intervals are not exposed. Legacy time_to_beat_* seconds remain available for compatibility; use the completion band for new integrations.
Example: POST /search/attributes selecting medium/high-confidence games in two bands, ordered shortest first:
{
"attributes": {
"time_to_complete.time_to_complete_band": { "$in": ["5-10h", "10-20h"] },
"time_to_complete.confidence": { "$in": ["high", "medium"] },
"time_to_complete.has_ending": true
},
"sort": { "field": "time_to_complete.time_to_complete_band", "direction": "asc" },
"pageSize": 25
} Band and confidence sorting follow the order listed above and put unavailable values last. time_to_complete.source filters igdb or prediction. Use "time_to_complete": { "$exists": false } to find games without completion evidence. Warning membership supports $eq, $in, $nin, $all and $exists; for example "time_to_complete.warnings": { "$in": ["open_ended", "low_label_support"] }.
GET /search/fields returns vocabularies.time_to_complete, including all warning labels and full explanations. The warning codes are open_ended, systemic_genre, early_access, unreleased, few_reviews, low_label_support, and single_igdb_submission. The last code concerns the IGDB evidence stored alongside the prediction. An empty warning list is distinct from missing completion evidence.
GET /reference/values/time_to_complete.time_to_complete_band, /reference/values/time_to_complete.confidence, and /reference/values/time_to_complete.warnings enumerate values with game counts. Reference reads cost no quota units.
MCP provides the same data and discovery. compact: true retains completion evidence. To request only it and game identities:
{
"name": "search_by_attributes",
"arguments": {
"attributes": { "time_to_complete.source": "prediction" },
"fields": ["app_id", "title", "time_to_complete"],
"pageSize": 10
}
} Nested selections also work: game.time_to_complete for get_game_report, games.time_to_complete for segment reports, and competitors.game.time_to_complete for concept reports. get_job_results applies those paths to each report.
Steam news activity
Every game row carries news, a summary of the game’s Steam news hub:
"news": {
"last_published_at": "2026-10-30T16:02:11.000Z",
"url": "https://store.steampowered.com/news/app/105600",
"days_since_last_news": 5.87,
"average_interval_days": 8.67
} | Field | Meaning |
|---|---|
days_since_last_news | Days since the latest post, calculated when your request is served (fractional) |
average_interval_days | Mean days between the latest (up to 50) posts; lower means more frequent posts |
last_published_at | Timestamp of the latest post |
url | The game’s Steam news hub |
These measure news publication activity, not verified patches: Steam’s news hub also carries announcements, events and articles from external feeds. news is null when the game’s news has never been collected; last_published_at and days_since_last_news are null when Steam lists no posts; average_interval_days is null with fewer than two posts.
Both measures are filterable and sortable, with missing values last in either direction. For example, games that posted within the last month at least every two weeks:
{
"attributes": {
"news.days_since_last_news": { "$lte": 30 },
"news.average_interval_days": { "$lte": 14 }
},
"sort": { "field": "news.average_interval_days", "direction": "asc" }
} Use { "news.days_since_last_news": { "$eq": null } } for games with no posts and { "news": { "$exists": false } } for games never collected. GET /search/fields describes both under vocabularies.news. The game report adds newsTimeline (the publication dates). Over MCP, compact: true keeps news.days_since_last_news and news.average_interval_days, including on games inside reports.
Store relationships
GET /games/{app_id}/relationships?relation&child_type&page&pageSize
Scope games:read, one unit. The game’s Steam store relationships as Steam listed them, one edge per row:
{
"success": true,
"data": [
{
"relation": "app_purchase_option",
"child_type": "package",
"child_id": 8183,
"position": 0,
"source_path": "package_groups[0].subs[0].packageid",
"context": {
"group": { "title": "Buy Terraria" },
"option": { "option_text": "Terraria - $9.99", "price_in_cents_with_discount": 999 }
},
"collection_complete": true,
"observed_at": "2026-11-04T05:45:10.070Z"
}
],
"meta": { "page": 1, "pageSize": 100, "total": 8 }
} relation | child_type | child_id is… | context |
|---|---|---|---|
app_dlc | app | a DLC’s Steam app id | null |
app_demo | app | the demo’s Steam app id | { description } |
app_package | package | a Steam package (sub) that includes the game | null |
app_purchase_option | package | a purchase option shown on the store page | Steam’s group and option (name and price text) |
Filter with relation and/or child_type; pageSize is up to 500 (default 100), since some games list thousands of DLC. An unknown app_id is 404; a game with no relationships returns an empty page. Most DLC are not in the Game Oracle catalogue, so they have no title here; link them with https://store.steampowered.com/app/{child_id} (packages: /sub/{child_id}).
The game report’s relationships holds the same data grouped: counts per relation, dlc, demos, purchase_options (with option_text, price_cents, percent_savings), the remaining packages, observed_at and complete. Over MCP, use get_game_relationships (same arguments plus app_id); compact: true on get_game_report keeps relationships.counts.