TarkovTracker provides internal API routes for fetching game data and team information. Game data is proxied through Nuxt server routes to json.tarkov.dev with caching and overlay corrections applied.
Set NUXT_TARKOV_JSON_BASE_URL to point static game-data requests at a compatible json.tarkov.dev mirror.
Upstream data source. TarkovTracker consumes the static JSON endpoints at
json.tarkov.dev. The endpoint catalog lives athttps://json.tarkov.dev/endpoints. The olderapi.tarkov.devGraphQL playground is deprecated and unstable; do not use it for game data or external tooling.
- Development:
http://localhost:3000/api - Production:
https://tarkovtracker.org/api
The progress API gateway (token, progress, team progress) is served on https://api.tarkovtracker.org (clean paths, /api/v2/* also accepted). The legacy https://tarkovtracker.org/api/v2/* routes remain served during the deprecation window.
Migration plan:
- TarkovMonitor >= the release containing tarkovtracker-org/TarkovMonitor#3 calls
api.tarkovtracker.orgdirectly. - Once that release has propagated, ops flip the gateway var
LEGACY_API_REDIRECTto"true"(seeworkers/api-gateway/wrangler.toml); legacy/apiand/api/v2requests then receive a308redirect to the subdomain withDeprecationandLink: rel="successor-version"headers. - Clients should migrate proactively rather than relying on the redirect: .NET
HttpClient(and several other HTTP stacks) drop theAuthorizationheader on cross-host redirects, so authenticated calls through the redirect will fail with401.
Migration example:
-POST https://tarkovtracker.org/api/v2/progress/task/{taskId}
+POST https://api.tarkovtracker.org/progress/task/{taskId}Tarkov data endpoints (/api/tarkov/*) are unauthenticated. Team endpoints require Supabase authentication.
Authorization: Bearer <supabase_jwt_token>First-party routes, not a third-party API.
/api/tarkov/*exists to serve game data to the TarkovTracker site itself. It is not a supported integration surface, carries no compatibility guarantee, and its response shape can change in any release. Third-party clients should read game data fromjson.tarkov.devdirectly, or use the progress API athttps://api.tarkovtracker.org(see Progress API Host Migration).These routes are public and pass through the API protection middleware; see
ARCHITECTURE.md#api-protectionfor access-control configuration andRATE_LIMITING.mdfor rate-limit ownership.
Fetches minimal player level data for early UI rendering.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code (see Supported Languages section) |
Response:
{
"data": {
"playerLevels": [
{ "level": 1, "exp": 0 },
{ "level": 2, "exp": 1000 }
]
}
}Cache TTL: 12 hours
Fetches core task data (tasks, maps, traders) without objectives or rewards.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Response:
{
"data": {
"tasks": [
{
"id": "5936d90786f7742b1420ba5b",
"name": "Debut",
"trader": { "id": "...", "name": "Prapor" },
"map": { "id": "...", "name": "Customs" },
"experience": 1500,
"minPlayerLevel": 1
}
],
"maps": [...],
"traders": [...]
}
}Cache TTL: 12 hours
Fetches task objectives and fail conditions.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 12 hours
Fetches task rewards (start, finish, failure).
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 12 hours
Fetches hideout stations with levels, requirements, and crafts.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 12 hours
Fetches lightweight item data (id, name, shortName, image).
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 24 hours
Fetches full item data including properties.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 24 hours
Fetches prestige level requirements.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
Prestige is intentionally sourced from regular/tasks and cached by language only because
json.tarkov.dev currently has no PvE prestige data.
Cache TTL: 24 hours
Fetches map spawn point data.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lang |
string | en |
Language code |
gameMode |
string | regular |
Game mode (regular, pve, or pvp-season) |
Cache TTL: 12 hours
Fetches cache purge timestamp to detect server-side cache clears.
Response:
{
"data": {
"lastPurgeAt": "2024-01-15T10:30:00.000Z"
}
}Cache TTL: Never cached (Cache-Control: no-store)
Fetches team member profiles. Requires authentication.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
teamId |
string | Yes | Team UUID |
Headers:
Authorization: Bearer <supabase_jwt_token>Response:
{
"members": ["user-uuid-1", "user-uuid-2"],
"profiles": {
"user-uuid-1": {
"displayName": "Player1",
"level": 45,
"tasksCompleted": 120,
"gameEdition": 4,
"gameMode": "seasonal"
}
}
}Errors:
| Status | Message | Cause |
|---|---|---|
| 400 | teamId is required | Missing teamId parameter |
| 401 | Missing auth token | No Authorization header |
| 401 | Invalid token | Invalid or expired JWT |
| 403 | Not a team member | User not in team |
Creates a Stripe Checkout session for a first supporter subscription or a one-time payment.
Existing active or past-due subscribers must use /api/stripe/portal to change plans. Requires
authentication.
Request Body (subscription):
{
"mode": "subscription",
"tier": "scav",
"interval": "monthly"
}Request Body (one-time payment):
{
"mode": "payment",
"amount": 10
}| Field | Type | Required | Description |
|---|---|---|---|
mode |
string | Yes | subscription or payment |
tier |
string | Subscription | scav, timmy, or chad |
interval |
string | Subscription | monthly, 6month, or yearly |
amount |
number | One-time | USD amount (min 1, max 999) |
Response:
{ "url": "https://checkout.stripe.com/c/pay/..." }Errors:
| Status | Message | Cause |
|---|---|---|
| 400 | Invalid tier / Invalid interval | Bad request body |
| 401 | Authentication required | Missing or invalid session |
| 409 | Manage your existing subscription in the billing portal | Active or past-due subscription exists |
| 500 | Stripe not configured | Server missing Stripe keys |
| 502 | Failed to create checkout session | Stripe API error |
| 503 | Unable to verify existing subscription | Supabase billing-state lookup is unavailable |
Creates a Stripe Customer Portal session so an authenticated subscriber can manage their subscription, payment method, or view invoices. Requires authentication and an existing Stripe customer linked to the user (via the supporters table).
Request Body:
{ "returnUrl": "https://tarkovtracker.org/supporter" }| Field | Type | Required | Description |
|---|---|---|---|
returnUrl |
string | No | Absolute URL on the configured app origin to send the user back to. Defaults to /supporter |
The returnUrl host must match the configured app URL host. Mismatched hosts fall back to ${appUrl}/supporter to prevent open-redirect abuse.
Response:
{ "url": "https://billing.stripe.com/p/session/..." }Errors:
| Status | Message | Cause |
|---|---|---|
| 401 | Authentication required | Missing or invalid session |
| 404 | No Stripe customer found | User has never paid; no stripe_customer_id row |
| 500 | Stripe not configured | Server missing Stripe keys |
| 502 | Failed to create billing portal session | Stripe API error |
This section covers external progress API quotas only (Worker + Durable Object). App mutation
limits, shared profile limits, Auth limits, and DB hard caps live in a separate ownership map:
RATE_LIMITING.md.
Progress API requests (api.tarkovtracker.org, /api/v2/*) are subject to tiered daily quotas keyed by user account (not per token). Daily quotas reset at 00:00 UTC and count authenticated requests admitted for processing.
| Tier | Reads/day | Writes/day |
|---|---|---|
| Free | 1,000 | 100 |
| Supporter | 2,000 | 250 |
| Scav | 2,000 | 250 |
| Timmy | 3,000 | 400 |
| Chad | 5,000 | 600 |
The gateway resolves the tier from public.supporters for the token owner and caches successful
lookups for up to 60 seconds. Active subscriptions and past-due subscriptions within their recorded
grace period keep paid limits; expired subscriptions return to Free limits.
A pre-authentication IP-based abuse gate (Cloudflare Workers Rate Limiting binding) shields the token validation step from floods. It is deliberately coarse — infrastructure protection, not a customer quota — and is not advertised as a per-IP entitlement.
Authentication and the daily-quota check run before request-input validation, so a malformed request
from an unauthenticated or over-quota client returns 401/429 rather than 400. Once a request is
authorized, validation 400 responses (malformed URL params, invalid JSON body) carry the same
X-RateLimit-* headers as successful responses — and, like them, omit those headers on the fail-open
path when no quota decision is available.
Responses for which the daily-quota service returns a quota decision include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). When that decision denies the request, the gateway responds with 429 and Retry-After. Pre-authentication abuse-gate 429 responses include only Retry-After, and fail-open responses (daily-quota service temporarily unavailable) omit the X-RateLimit-* headers. When a free-tier user exhausts a daily quota, the 429 body includes an upgrade link. Admins can inspect the top consumers via GET /api/admin/api-usage; usage is bucketed by UTC day, so the report covers the current and previous UTC day (the since field gives the exact starting day).
GET /progress and GET /team/progress return a weak ETag derived from the response payload and use Cache-Control: private, max-age=15. Response bodies of 1 KiB or larger are gzipped when the request includes Accept-Encoding: gzip; smaller payloads are sent uncompressed, so do not assume Content-Encoding is always present. An explicit Accept-Encoding: gzip;q=0 is honored as a refusal. If a client accepts gzip but explicitly refuses identity (identity;q=0), even sub-1 KiB payloads are gzipped since uncompressed is not acceptable to that client; if no acceptable encoding remains the gateway returns 406 no_acceptable_encoding. Send the previous response's ETag in If-None-Match; when nothing changed the gateway answers 304 Not Modified with an empty body. Rate-limit headers (X-RateLimit-*) are included on 200 and 304 responses only when a daily-quota decision is available — on the fail-open path (daily-quota service temporarily unavailable) those headers are omitted but the ETag/304 mechanism still applies. A 304 still counts against the daily quota, so it saves bandwidth, not quota.
Polling integrators (TarkovMonitor, tarkov.dev, RatScanner) should poll read endpoints at ≥60-second intervals and always send If-None-Match. Idle accounts then cost a few hundred bytes per poll instead of a full progress payload.
Each account may have at most 3 active API tokens. This is enforced by a database trigger, so token rotation cannot bypass it. The token-create Edge Function returns 409 with error: "Token limit reached (3 active)" when the cap is reached. Revoke an existing token before creating a new one. Token creation is only allowed through the token-create Edge Function (authenticated clients cannot insert into api_tokens directly) and is rate-limited to 3 creates per hour per account.
Token names can be changed from Settings → API Tokens without rotating the token. Renaming updates
only the token's optional note: authenticated users receive column-level UPDATE (note) permission,
and row-level security requires the token owner for both the existing and resulting row.
Progress API tokens are prefixed PVP_, PVE_, or SZN_. The prefix declares the token's mode,
and the token's stored game_mode decides which exact normalized (user_id, game_mode, season_number)
progress row every read and write touches, never the mode the owner is currently viewing on the
site. Persistent PvP/PvE use season 0; Seasonal tokens use the active season. The token prefix and
stored mode are kept from diverging at three layers:
token-createrejects agameModeoutsidepvp/pve/seasonalwith400, and rejects a suppliedtokenValuewhose prefix contradictsgameModewith400 tokenValue prefix must match gameMode.- A
NOT VALIDcheck constraint onapi_tokensrequirestoken_valueto carry the prefix matchinggame_mode. - The gateway rejects a token whose prefix disagrees with its stored
game_modewith401 Token game mode mismatchinstead of silently serving the stored mode.
Legacy tt_ tokens are no longer accepted; they fail with 401 Invalid token format. Create a
PVP_/PVE_/SZN_ token in Settings → API Tokens instead.
Nuxt/Pages /api/* routes return errors in this format:
{
"error": "Error message",
"statusCode": 500,
"statusMessage": "Internal Server Error"
}The public API gateway (api.tarkovtracker.org) uses its own envelope,
{"success": false, "error": "..."}. Unexpected gateway failures always return 500 with the fixed
body {"success": false, "error": "Internal server error"}; the underlying exception is logged
server-side only, so clients must not parse 500 bodies for diagnostic detail. Client-correctable
problems keep their specific 4xx messages in the same envelope.
The client caches API responses in IndexedDB with keys like:
tarkov-tasks-core-regular-entarkov-hideout-pve-detarkov-tasks-core-pvp-season-entarkov-items-lite-regular-entarkov-prestige-all-regular-en
Cloudflare edge caching with Cache-Control headers:
Cache-Control: public, max-age=43200Note: 43200 seconds = 12 hours (default), 86400 seconds = 24 hours (extended)
Pass cacheBust=1 query parameter to bypass cache.
The lang query parameter is validated against API_SUPPORTED_LANGUAGES (app/utils/constants.ts); codes outside that allowlist fall back to en (getValidatedLanguage in app/server/utils/language-helpers.ts).
lang is not forwarded to upstream as a query parameter. json.tarkov.dev serves an English base document containing translation keys plus a separate per-language document at {gameMode}/{endpoint}_{lang}; the proxy fetches both (plus _en as a per-key fallback) and merges them via the base document's translations JSONPath list. See Data fetching pipeline.
Language codes accepted by /api/tarkov/*:
| Code | Language |
|---|---|
cs |
Czech |
de |
German |
en |
English |
es |
Spanish |
fr |
French |
hu |
Hungarian |
it |
Italian |
ja |
Japanese |
ko |
Korean |
pl |
Polish |
pt |
Portuguese |
ro |
Romanian |
ru |
Russian |
sk |
Slovak |
tr |
Turkish |
zh |
Chinese |
This allowlist is a subset of what upstream serves. json.tarkov.dev additionally supports id, th, and vn; add them to API_SUPPORTED_LANGUAGES when the API should accept those languages on /api/tarkov/* requests. Enabling a UI locale is a separate step (SUPPORTED_LOCALES below). The authoritative upstream list is the languages array at https://json.tarkov.dev/endpoints.
Enabled UI locales (SUPPORTED_LOCALES in app/utils/locales.ts):
cs (Czech), de (German), en (English), es (Spanish), fr (French), it (Italian), ko (Korean), pl (Polish), pt (Portuguese), ru (Russian), uk (Ukrainian), zh (Chinese)
UI locale with upstream fallback. uk (Ukrainian) is an enabled UI locale but is not supported by json.tarkov.dev. It is mapped to en via LOCALE_TO_API_MAPPING in app/utils/constants.ts, so Ukrainian users see English game data while the rest of the UI remains in Ukrainian.
| Mode | Description |
|---|---|
regular |
Persistent standard PvP mode |
pve |
Persistent PvE (Co-op) mode |
pvp-season |
Numbered Seasonal PvP game-data mode |
The application keeps the stable internal mode name seasonal and maps it to the upstream
pvp-season endpoint. The active season number is stored separately from the mode so future
seasons create new progress rows without overwriting earlier Seasonal history.
All task data is enhanced with community corrections from the tarkov-data-overlay repository.