NTPrices
Deals All Regions Sales Blog

eShop Game Price API — v2 Reference

This is the current, recommended API (v2). The legacy v1 endpoint (/api.php) still works but is deprecated and will be retired on 1 December 2026. New integrations should use v2. ← Back to the API overview & pricing.

The NTPrices eShop Game Price API v2 is a REST API returning clean JSON for Switch & Switch 2 prices, price history and live sales/discounts across 70+ regions. The base URL is:

https://ntprices.com/api/v2

Quickstart — from key to first JSON in 5 minutes

Just received your API key by email? Follow these steps in order — especially step 2 (regions), the thing most new integrations trip over. No key yet? Open the keyless /demo in your browser to see the data shape, and grab a key via the plans page.

The key stays in your browser only (it is never sent to this page’s server). Links from our emails of the form /api/v2/docs#key=… do this automatically.

Step 1 — check that your key works

GET /account is free (it never consumes a request) and shows everything about your key: plan, monthly quota, expiry and — crucially — which store regions it can query.

curl -H "X-API-Key: $PP_KEY" "https://ntprices.com/api/v2/account"
{
  "success": true,
  "data": {
    "plan": "indie",
    "monthly_limit": 20000,
    "remaining_this_month": 20000,
    "regions": {
      "allowed_regions": ["DE", "FR", "GB", "JP", "US"],   // <-- what you may query
      "region_limit": 5,
      "source": "plan_default",
      "can_change_now": true
    }
  }
}

Getting 401 instead? Make sure the header is exactly X-API-Key and the key was copied without spaces or line breaks.

Step 2 — set the regions you actually need (the #1 gotcha)

Every data request is scoped to one store region via the region= parameter — and if you omit it, it defaults to US. New keys start with your plan’s default region set (for Indie: US, GB, DE, FR, JP). Requesting a region that is not enabled for your key returns 403 REGION_NOT_IN_PLANthat is configuration, not a broken key. Pick your own set (up to your plan’s limit) with one call:

# Example: an Indie key (5 regions) that needs Ukraine + Turkey
curl -X PUT -H "X-API-Key: $PP_KEY" -H "Content-Type: application/json" \
     -d '{"regions":["US","GB","UA","TR","DE"]}' \
     "https://ntprices.com/api/v2/account/regions"

The change is immediate. Valid codes come from GET /regions; regions can be changed once every 24 hours (re-submitting the same list is a free no-op), so pick the set you need before you start. The same control exists on your developer dashboard.

Step 3 — your first data request

# Search a game by name in the Turkish store
curl -H "X-API-Key: $PP_KEY" \
  "https://ntprices.com/api/v2/games/search?q=Resident+Evil+4&region=tr"

# Everything on sale in the Ukrainian store, biggest discounts first
curl -H "X-API-Key: $PP_KEY" \
  "https://ntprices.com/api/v2/deals?region=ua&sort=discount&order=desc&limit=20"

Remember: always pass region= explicitly — it makes your intent unambiguous and your caching keys clean.

Step 4 — the same call in your language

// JavaScript (Node 18+ / browser)
const res  = await fetch("https://ntprices.com/api/v2/deals?region=ua&limit=20", {
  headers: { "X-API-Key": "$PP_KEY" }
});
const json = await res.json();
if (!json.success) throw new Error(json.error.code + ": " + json.error.message);
console.log(json.data.length, "deals; first:", json.data[0].ProductName);
# Python 3 (requests)
import requests

r = requests.get(
    "https://ntprices.com/api/v2/deals",
    params={"region": "ua", "limit": 20},
    headers={"X-API-Key": "$PP_KEY"},
    timeout=15,
)
j = r.json()
if not j["success"]:
    raise RuntimeError(f"{j['error']['code']}: {j['error']['message']}")
for game in j["data"]:
    print(game["ProductName"], game["formattedSalePrice"])
<?php // PHP 8 (cURL)
$ch = curl_init("https://ntprices.com/api/v2/deals?region=ua&limit=20");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['X-API-Key: $PP_KEY'],
    CURLOPT_TIMEOUT        => 15,
]);
$j = json_decode(curl_exec($ch), true);
if (!($j['success'] ?? false)) {
    throw new RuntimeException($j['error']['code'] . ': ' . $j['error']['message']);
}
foreach ($j['data'] as $game) {
    echo $game['ProductName'], ' — ', $game['formattedSalePrice'], "\n";
}

Step 5 — watch your quota, handle errors

Every response carries X-RateLimit-Remaining (and meta.rate_limit); GET /status and GET /account/usage are free ways to check usage. Always branch on error.code — the most common first-day issues:

You seeWhat it means & the fix
401 MISSING_KEYThe X-API-Key header didn’t arrive — check the header name and that your HTTP client actually sends it.
401 INVALID_KEYKey not recognised — usually a copy/paste issue (whitespace, truncation). Copy it again from the key email.
403 REGION_NOT_IN_PLANThe region isn’t enabled for your key. Fix it yourself in 10 seconds — see step 2.
400 INVALID_REGIONUnknown region code — use a 2-letter code from GET /regions.
429 RATE_LIMIT_EXCEEDEDMonthly quota or the 120 req/min/IP burst limit — honour Retry-After and cache responses.

Full error reference is below. Stuck? Email [email protected] with the request URL and the JSON error you got — we answer fast.

Authentication

Every request (except /demo) needs an API key, sent in the X-API-Key request header. A ?key= query parameter is also accepted for backward compatibility with v1.

curl -H "X-API-Key: $PP_KEY" "https://ntprices.com/api/v2/games?region=us&limit=5"

Keys are issued by email — tell us about your project at [email protected]. Keep your key secret; treat it like a password.

Fully self-service by key — no website account needed. Everything about your key is manageable through the API itself: GET /account (plan, quota, expiry, regions), GET /account/usage (daily request history + per-endpoint breakdown) and PUT /account/regions (choose which store regions your key serves). All three are free — they never consume a request. Prefer a UI? The same controls live on your developer dashboard.

# Choose your regions — by key alone, no account needed
curl -X PUT -H "X-API-Key: $PP_KEY" -H "Content-Type: application/json" \
     -d '{"regions":["US","GB","DE"]}' "https://ntprices.com/api/v2/account/regions"

Plans & limits

Quotas are counted per calendar month and reset on the 1st (UTC). Region access and price history depend on your plan.

PlanPriceRequests / monthRegionsPrice history
Free $0 1,000 2
Indie $19/mo 20,000 5 3 months
Pro $49/mo 100,000 20 3 months
Business $149/mo 500,000 All 50 12 months

Price history (/games/{ppid}/price-history) is a paid feature: on the Free plan it returns 403 PLAN_UPGRADE_REQUIRED. The history window is plan-tied — Indie and Pro see the last 3 months, Business the last 12 months; need a deeper or full-archive export? Talk to us. If you request a region outside your plan you get 403 REGION_NOT_IN_PLAN — configure your allowed regions or upgrade.

Sale archive follows the same window: /sales/{id} serves any active or recently ended sale, but a sale that ended beyond your plan’s history window returns 403 SALE_ARCHIVE_WINDOW.

Terms of use. Use the API in your apps, dashboards, analytics and alerts. Don’t build a directly competing price-tracking service, resell or redistribute the raw data, bulk-archive beyond your plan’s limits, or scrape the website to get around them. Where your plan requires attribution, show “Powered by NTPrices” with a link. Violating keys may be throttled or revoked.

Rate limiting

Each response carries your live usage in headers:

HeaderMeaning
X-RateLimit-LimitYour monthly request quota.
X-RateLimit-UsedRequests used this month.
X-RateLimit-RemainingRequests left this month.
X-RateLimit-ResetUnix epoch (UTC) when the quota rolls over — also in the body as meta.rate_limit.reset_at (ISO 8601).
X-Response-Time-MsServer-side processing time in milliseconds (also meta.response_ms).
Retry-AfterOn 429: seconds until the limit frees up.

When the monthly quota is exhausted you get 429 RATE_LIMITED (with Retry-After = seconds to the 1st of next month). There is also a short-term burst limit of 120 requests per minute per IP to protect the service; exceeding it returns 429 with Retry-After to the next minute. Please cache responses in your own database rather than re-requesting the same data.

Response format

Every response is a JSON object with the same envelope:

{
  "success": true,
  "data": { ... } | [ ... ] | null,
  "meta": {
    "region": "US",
    "pagination": { "page": 1, "limit": 20, "total": 134 },
    "rate_limit": { "limit": 50000, "used": 12, "remaining": 49988, "reset_at": "2026-07-01T00:00:00Z" },
    "response_ms": 14
  },
  "error": null
}

On failure, success is false, data is null, and error is { "code": "STRING_CODE", "message": "…", "status": 4xx }. Errors your integration is expected to handle (e.g. REGION_NOT_IN_PLAN) additionally carry a machine-readable error.details object — for the region gate it includes requested_region, enabled_regions and the exact self-service call to enable it, so you can branch on fields instead of parsing the message. All responses also send X-API-Version: 2 and permissive CORS headers (Access-Control-Allow-Origin: *, methods GET, PUT, POST, OPTIONS).

Errors

HTTPerror.codeWhen
400INVALID_PARAM / INVALID_REGION / INVALID_SORTA query parameter is missing or invalid.
401MISSING_KEY / INVALID_KEYNo key supplied, or the key is not recognised.
403PLAN_UPGRADE_REQUIRED / REGION_NOT_IN_PLANYour plan does not include this feature or region. REGION_NOT_IN_PLAN includes error.details with your enabled regions and the PUT /account/regions call that enables the requested one.
404GAME_NOT_FOUND / SALE_NOT_FOUND / ROUTE_NOT_FOUNDThe resource or route does not exist.
405METHOD_NOT_ALLOWEDData endpoints are GET-only; PUT/POST exist only under /account.
429RATE_LIMIT_EXCEEDED / REGION_CHANGE_COOLDOWNMonthly quota or per-minute burst limit exceeded; or a region change inside the 24h cooldown.
500INTERNAL_ERRORSomething went wrong on our side.

Common query parameters

ParameterApplies toDescription
regionmost endpoints2-letter region code (e.g. us, gb, de, jp). Defaults to your account region. See /regions.
platform/games, /dealsFilter by ps4 (base Switch), ps5 (Switch 2), vr or move.
min_discount/games, /dealsOnly games discounted at least this percent (e.g. 50).
sort/games, /dealsField to sort by — the valid keys differ per endpoint (see each endpoint below).
orderlist endpointsasc or desc.
limitlist endpointsResults per page (capped server-side).
pagelist endpointsPage number (1-based).
with_totallist endpoints1 to include the total count in meta.pagination.total.
group_mode/gamesReturn unified game “cards” (group editions of the same game).
include_related/games/{ppid}1 to include related editions/DLC.
q/games/searchSearch text (game name).
fieldsgame endpointsComma-separated whitelist — return only these fields (e.g. fields=PPID,ProductName,SalePrice).

The game object

The game/product endpoints (/games, /games/{ppid}, /games/search, /deals, /demo…) all return the same object. Notable fields, grouped:

GroupFields
IdentityPPID (int), NSUID (eShop NSUID), ProductName, region, Img, NTPricesURL
PlatformIsSwitch (base Switch), IsSwitch2 (Switch 2), IsVR, IsDLC, IsDemoOrSoundtrack, SwitchSize, Switch2Size
PricingBasePrice, SalePrice, DiscPerc, LowestEverPrice — plus a formatted… string for every price
ReviewsOpenCriticID — use with the OpenCritic API for review scores
OtherRating, RatingDesc, NumLibrary, NumWishlist, genre flags

Prices are integers in the region’s minor units (scaled by decimalPlaces from /regions) — for display, use the matching formatted… string. LowestEverPrice is null when no record exists. Internal columns are never returned; use fields= to narrow the object further.

Removed: the legacy trophy fields Bronze, Silver, Gold, Platinum, Difficulty, HoursLow, HoursHigh and TrophyListURL — plus the has_platinum, difficulty, difficulty_max and hours_max query filters — are no longer returned or accepted. Trophies are a PlayStation concept with no Nintendo Switch equivalent; these never carried meaningful data for eShop titles. Requests using those filters simply ignore them.

Renamed (breaking): the PSNID field and the by-psnid endpoint / ?psnid= param were renamed to NSUID / by-nsuid / ?nsuid= (PlayStation → Nintendo eShop — the value was always the 14-digit eShop NSUID). The legacy ?psnid= param and /games/by-psnid/{psnid} path are kept as deprecated aliases.

Endpoints

Quick reference — tap a path to jump to its full details.

EndpointReturns
GET /games Filterable, paginated list of games with prices and discounts.
GET /games/search Search games by name.
GET /games/{ppid} A single game by its NTPrices ID (the PPID integer).
GET /games/by-nsuid/{nsuid} A single game by its eShop product ID / NSUID (e.g. 70010000012345). The legacy /games/by-psnid/{psnid} path still works as a deprecated alias.
GET /games/{ppid}/price-history Price history for a game. — Free returns 403 PLAN_UPGRADE_REQUIRED. Depth depends on your plan: Indie and Pro see the last 3 months, Business the last 12 months.
GET /deals Everything currently discounted, sorted by your choice.
GET /sales Active sale campaigns (e.g. “Big in Japan”), not individual games. Returns all active sales (no pagination).
GET /sales/{id} All games inside one sale campaign. Sales that ended beyond your plan’s history window return 403 SALE_ARCHIVE_WINDOW.
GET /regions Every supported eShop region. Cached 24h.
GET /status Your key’s plan, quota and usage. Cheap way to check how many requests you have left.
GET /account Your key’s full self-service view — everything the developer dashboard shows, by API key alone (no website account needed). Free: does not consume a request.
GET /account/usage Your key’s request history — the same counters that feed the dashboard chart. Free: does not consume a request.
PUT /account/regions Choose the store regions your key serves — entirely via the API, no website account needed. Up to your plan’s region limit, at most once every 24 hours (shared with the dashboard’s cooldown). POST is accepted as an alias; GET returns the current configuration without changing it. Re-submitting the identical list is a no-op and does not burn the daily change.
GET /demo Keyless sample — open it straight in your browser. IP rate-limited.
GET / API index — name, version and the list of available endpoints.
GET https://ntprices.com/api/v2/games

Filterable, paginated list of games with prices and discounts.

ParameterDescription
region2-letter region code (default: your account region).
platformswitch (base Switch), switch2 (Switch 2) or vr.
genreGenre token (e.g. rpg, action, fps, horror, racing…).
on_sale1 for currently-discounted games only.
min_discountOnly games discounted ≥ this percent.
price_min / price_maxPrice range (region minor units).
exclude_dlc / exclude_demo1 to omit DLC / demos.
publisher / publisher_likeFilter by publisher (exact / partial match).
sortpopularity, price, sale_price, discount, name, release or lowest_ever.
orderasc / desc.
limit / pagePagination (page is 1-based).
with_total0 to skip the total count (on by default).
group_mode1 to collapse editions/DLC into one card per game.
fieldsComma-separated list to return only those fields.

Returns: An array of game objects. With group_mode=1 it instead returns an array of cards — a representative game object plus edition_group_id, edition_count and an editions[] array (each a slim object with an is_representative flag). meta.pagination { page, limit, total }.

GET https://ntprices.com/api/v2/games/{ppid}

A single game by its NTPrices ID (the PPID integer).

ParameterDescription
regionRegion code.
include_related1 to also include related editions/DLC.
fieldsField projection.

Returns: One game object. With include_related=1 it also carries a related[] array of slim objects.

GET https://ntprices.com/api/v2/games/by-nsuid/{nsuid}

A single game by its eShop product ID / NSUID (e.g. 70010000012345). The legacy /games/by-psnid/{psnid} path still works as a deprecated alias.

ParameterDescription
regionRegion code.
include_related1 to also include related editions/DLC.
fieldsField projection.

Returns: One game object (same shape as /games/{ppid}).

GET https://ntprices.com/api/v2/games/{ppid}/price-history

Price history for a game. — Free returns 403 PLAN_UPGRADE_REQUIRED. Depth depends on your plan: Indie and Pro see the last 3 months, Business the last 12 months.

ParameterDescription
regionRegion code.

Returns: An object { ppid, region, history[] }. Each history point: timestamp, BasePrice, SalePrice (+ formatted… strings) and isLowestEverPrice (boolean). meta.count = number of history points; meta.history_window_months = your plan’s window.

GET https://ntprices.com/api/v2/deals

Everything currently discounted, sorted by your choice.

ParameterDescription
regionRegion code.
platformswitch / switch2.
min_discountMinimum discount percent.
sortrecent (default), discount, price or name.
orderasc / desc.
limit / pagePagination.
fieldsField projection.

Returns: An array of game objects that are on sale right now (each has a non-zero DiscPerc). meta.pagination.

GET https://ntprices.com/api/v2/sales

Active sale campaigns (e.g. “Big in Japan”), not individual games. Returns all active sales (no pagination).

ParameterDescription
regionRegion code.

Returns: An array of sale objects: id, name, startsAt, endsAt, numGames, imageURL, url (link to that sale’s endpoint). meta.count.

GET https://ntprices.com/api/v2/sales/{id}

All games inside one sale campaign. Sales that ended beyond your plan’s history window return 403 SALE_ARCHIVE_WINDOW.

ParameterDescription
regionRegion code.

Returns: An object: id, name, region, startsAt, endsAt, imageURL, plus game_discounts[] and dlc_discounts[] — each a compact game object (PPID, NSUID, ProductName, Img, prices + formatted…). meta { game_count, dlc_count }.

GET https://ntprices.com/api/v2/regions

Every supported eShop region. Cached 24h.

Returns: An array of region objects: region (code), language, currencySymbol, decimalPlaces, and inYourPlan (whether your plan can query it). meta.count.

GET https://ntprices.com/api/v2/status

Your key’s plan, quota and usage. Cheap way to check how many requests you have left.

Returns: An object: plan, plan_name, monthly_limit, used_this_month, remaining_this_month, quota_period (“month”), reset_at_utc, allowed_regions (“all” or an array), commercial_use, attribution_required.

GET https://ntprices.com/api/v2/account

Your key’s full self-service view — everything the developer dashboard shows, by API key alone (no website account needed). Free: does not consume a request.

Returns: An object: key_prefix, plan, plan_name, monthly_limit, used_this_month, remaining_this_month, used_today, reset_at_utc, expires_at_utc (null = never), max_records_per_list_request, price_history_months, commercial_use, attribution_required, and a regions object (allowed_regions, region_limit, source, can_change_now, next_change_allowed_at_utc).

GET https://ntprices.com/api/v2/account/usage

Your key’s request history — the same counters that feed the dashboard chart. Free: does not consume a request.

ParameterDescription
daysWindow in days, 1–90 (default 30; counters are retained 90 days).

Returns: An object: window_days, total_requests, busiest_day {date, requests}, daily[] (zero-filled {date, requests} series, oldest first — chart-ready) and endpoints[] (top routes by volume, e.g. v2/games/{id}).

PUT https://ntprices.com/api/v2/account/regions

Choose the store regions your key serves — entirely via the API, no website account needed. Up to your plan’s region limit, at most once every 24 hours (shared with the dashboard’s cooldown). POST is accepted as an alias; GET returns the current configuration without changing it. Re-submitting the identical list is a no-op and does not burn the daily change.

ParameterDescription
regionsJSON body {"regions":["US","GB"]} (or a "US,GB" string); a ?regions=US,GB query parameter also works. Codes must come from /regions; unknown codes return 400 INVALID_PARAM.

Returns: The new region configuration (same regions object as /account) plus changed (boolean). During the cooldown it returns 429 REGION_CHANGE_COOLDOWN with a Retry-After header.

GET https://ntprices.com/api/v2/demo

Keyless sample — open it straight in your browser. IP rate-limited.

Returns: A fixed sample of 10 game objects, same shape as /games (no key, no pagination). The same titles every call — it shows the data shape, not a live feed. meta { note, count }.

GET https://ntprices.com/api/v2/

API index — name, version and the list of available endpoints.

Returns: An object: name, version, status, base_url, authentication, documentation, endpoints[].

Examples

# Trending deals in the UK, biggest discounts first
curl -H "X-API-Key: $PP_KEY" \
  "https://ntprices.com/api/v2/deals?region=gb&sort=discount&order=desc&limit=20"

# One game by eShop product ID / NSUID, with related editions
curl -H "X-API-Key: $PP_KEY" \
  "https://ntprices.com/api/v2/games/by-nsuid/70010000012345?region=us&include_related=1"

# Price history (paid plans; 3 months back, Business 12)
curl -H "X-API-Key: $PP_KEY" \
  "https://ntprices.com/api/v2/games/7704/price-history?region=us"

# No key needed — try this one in your browser:
curl "https://ntprices.com/api/v2/demo"

The keyless /demo endpoint returns a real sample response so you can see the shape of the data before you get a key.

Terms of use

PermittedNot permitted
Building apps, sites and tools on top of the data; caching results in your own database; commercial use on a paid plan. Re-selling or redistributing the raw dataset as-is; scraping around the rate limits; bulk-mirroring the entire catalogue.

On the Free and Indie plans, please show a “Powered by NTPrices” attribution wherever the data appears. Pro and Business plans have no attribution requirement. We may throttle or revoke keys that abuse the service. Commercial use requires a paid plan.

Questions, a higher limit, or a custom plan? Email [email protected].

↑ Top
We value your privacy. We use strictly-necessary cookies to run the site (sign-in, security). With your permission we also use analytics and advertising cookies to measure traffic and fund the site. You can accept all, reject non-essential, or choose per category. See our Cookie & Privacy Policy.