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

ScopeGrants
search:readAll /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:writeAll /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

ControlDefault
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, every GET /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-Remaining and X-RateLimit-Reset (Unix seconds). When Remaining reaches one or two, wait until Reset before the next call rather than retrying on a 429. A Retry-After header 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

HeaderDescription
X-RateLimit-LimitPer-minute cap for this key
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix timestamp when the window resets
X-Quota-LimitThe account’s monthly quota
X-Quota-UsedUnits 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.

StatusCodes
400VALIDATION_ERROR
401INVALID_KEY
403INSUFFICIENT_SCOPE, SUBSCRIPTION_REQUIRED, SEAT_REVOKED
404NOT_FOUND
409JOB_NOT_FINISHED, JOB_NOT_ACTIVE, JOB_IN_PROGRESS
429RATE_LIMITED, QUOTA_EXCEEDED, TOO_MANY_ACTIVE_JOBS
500SERVER_ERROR
502UPSTREAM_ERROR
503SERVICE_UNAVAILABLE, CONCURRENCY_LIMIT (retry after the Retry-After header)

Five endpoints share one result format. All are POST with a JSON body.

EndpointRanks by
/search/titleSimilarity to an existing game (appId)
/search/descriptionSimilarity to a free-text description (query)
/search/imageVisual similarity to one or more images
/search/attributesNo ranking — structured filters only, sortable
/search/combinedRank fusion of any mix of title, description and images

Common body fields

FieldTypeDefaultNotes
nNeighboursinteger 1–100001000Semantic searches only: how many closest games to consider
pageinteger ≥ 11
pageSizeinteger 1–10025
filtersobject—The Data Explorer advanced filters (below)
attributesobject—Free-form filters over any catalogue field (below)
sort{ field, direction }app_id ascAttribute 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".

FieldType / values
includeTags, excludeTagsstring[]
releasedOrUnreleasedReleased | Unreleased | Both
earlyAccess, aiDisclosureInclude | Exclude | Only
pricingTypeFree | Paid | Both
devTypeSelf-Published | AAA | All
minSteamDBScore, minPercPositiveReviewsnumber 0–100
minReviewCount, minPeerRank, minUniquenessScorenumber ≥ 0
excludeNSFWboolean
releaseDateStart, releaseDateEndYYYY-MM-DD
wishlistRange, unitsSoldRange, revenueRange, devLifetimeRevenueRange, pubLifetimeRevenueRange, initialPriceRange, latestPriceRange[min, max]
gameEnginestring (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.

EndpointReturns
GET /search/fieldsEvery filterable/sortable field with its operators, plus filters — the enumerated options of each filters key — and reference links to the endpoints below
GET /reference/tagsEvery 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/segmentsThe vocabulary of GET /game-gap/segments: dimensionalities, the live subcategory list per dimensionality, sortBy keys, exclusion flags, range parameters
GET /reference/concept-optionsThe tag lists, enums, defaults and limits POST /concept-compass/concepts validates against
GET /reference/featuresSearch 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.

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.

POST /search/description
{ "query": "A cozy farming sim where you rebuild a lighthouse town", "nNeighbours": 1000 }

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.

POST /search/attributes
{
	"attributes": { "coming_soon": true, "estimated_wishlist.mid": { "$gte": 20000 } },
	"filters": { "excludeNSFW": true },
	"sort": { "field": "estimated_wishlist.mid", "direction": "desc" },
	"pageSize": 100
}
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:

FieldContents
gameEvery catalogue field for the game
socialsStore and social links
tagsThe game’s tags grouped: genres, themes, visuals, playerSupport, steamFeatures, other
benchmarksSteam-wide baselines every stat is compared against
segmentsThe market segments the game belongs to (with displayTitle)
playerHistoryMonthly average and peak concurrent players
newsTimeline{ dates }: up to 50 Steam news publication dates, newest first; null if never collected
relationshipsDLC, demos, purchase options and packages from the store page, grouped (see Store relationships)
reviewSummaryReview analysis: overall, positives[], negatives[]
similarGamesThe 100 most similar games, closest first, each with distance
generationWhether 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):

FieldMeaning
estimated_wishlistAll-time wishlists
estimated_wishlist_pre_launchWishlists the game had when it launched (first day on sale)
estimated_wishlist_post_launchWishlists 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.

ParameterValues
dimensionalityBoth | 2D | 3D
subcategoryA subcategory name, or __NULL__ for top-level segments
keywordSubstring match on the segment’s keywords
sortBydemand_score | avg_uniqueness_score | median_estimated_steam_sales (default: top picks)
excludeGoliaths, excludeStampede, excludeAchievementFarming, excludeCultFollowing, excludeNsfw (or excludeNSFW), excludeFranchiseDominatedtrue to exclude that segment type
minPercComingSoon/maxPercComingSoon, minPercIndie/maxPercIndie, minPercVpos/maxPercVposfractions 0–1
minMedianSales/maxMedianSales, minMedianWishlists/maxMedianWishlists, minMedianRevenue/maxMedianRevenue, minMedianDevLifetimeRevenue/maxMedianDevLifetimeRevenue, minMedianUniquenessScore/maxMedianUniquenessScore, minMedianAge/maxMedianAgenumbers
clusterIdsComma-separated ids (≤100) — fetch specific segments instead of filtering
page, pageSizepageSize ≤ 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:

FieldContents
segmentThe segment row: composition percentages, medians, trend figures, warning flags, app_ids
titleDisplay title
gamesEvery game in the segment (full catalogue rows)
overviewdetailed_summary, five_similar_features, market_gaps, biggest_opportunities, biggest_challenges, developer_fit
sentimentPlayer sentiment: overview, positives[], negatives[]
benchmarksSteam-wide baselines
metricsDerived 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
generationWhether 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/projects paths, the projectId field and the create_concept_project MCP 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:

FieldContents
inputThe wizard fields
competitors[]Each competitor’s full catalogue row plus distance, relevance (Very/Somewhat), relevanceReason, discoveryMethod
headlineuniquenessScore, sales, reviews, revenue, wishlists (unreleased competitors), wishlistsPreLaunch, wishlistsPostLaunch, wishlistsAllTime, wishlistConversion (mid/low/high), priceRange, counts, validPublisherIds
projectionPer-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
boxleiterMultipliersThe review-to-sales multiplier range used for the cross-check
resourcessuggestions (technical components, visuals, audio, level design, hard problems, search/asset keywords) and suggestedPublishers
marketingsuggestedSteamDescription, ideas[], suggestedTags
competitionsummary, playerSentiment
gdddraft, markdown, generatedAt, editedAt, inspirationCompetitorIds. Filled only for older concepts that have a Draft GDD; each is null for new ones
generationgeneratedFromCompetitorIds 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
}
FieldMeaning
days_since_last_newsDays since the latest post, calculated when your request is served (fractional)
average_interval_daysMean days between the latest (up to 50) posts; lower means more frequent posts
last_published_atTimestamp of the latest post
urlThe 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 }
}
relationchild_typechild_id is…context
app_dlcappa DLC’s Steam app idnull
app_demoappthe demo’s Steam app id{ description }
app_packagepackagea Steam package (sub) that includes the gamenull
app_purchase_optionpackagea purchase option shown on the store pageSteam’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.