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 projects. It is available to every member of an active Business account.

Base URL: https://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 project 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. $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.*, developers_lifetime_revenue.*, store_release_date.*, latest_release_date.*, age_normalised_rank.*, pc_min_requirements.*, 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/projects 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://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
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.

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, 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

POST /concept-compass/projects

Create a project from the same inputs the Concept Compass wizard collects. The project appears in your Concept Compass project list immediately and is filled in as the job runs.

POST /concept-compass/projects
{
	"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",
		"projectId": "…",
		"pollUrl": "/api/business/v1/jobs/…",
		"reportUrl": "/api/business/v1/concept-compass/projects/…",
		"items": [
			{ "id": "landscape", "status": "pending" },
			{ "id": "resources", "status": "pending" },
			"…"
		]
	}
}

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/projects/{projectId}

While a job is writing to the project: 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, 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
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
generationgeneratedFromCompetitorIds and drift (how far the competitor list has moved since)

POST /concept-compass/update-competitors/{projectId}

POST /concept-compass/update-competitors/{projectId}
{ "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 project already has a running job. Costs 3 units.


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.