{"openapi":"3.0.3","info":{"title":"WoW Token Price API","version":"0.1.0","description":"Public REST API for real-time and historical World of Warcraft Token prices. Sourced from the official Blizzard Game Data API. Endpoints flagged as `planned` are documented for contract-first design but are not yet implemented.","contact":{"name":"WoW Token Price","url":"https://wowtokenprice.com"},"license":{"name":"MIT"}},"servers":[{"url":"/api","description":"API root"}],"tags":[{"name":"meta","description":"Service metadata and documentation"},{"name":"tokens","description":"WoW Token price data"}],"paths":{"/docs":{"get":{"tags":["meta"],"summary":"Get the OpenAPI specification","description":"Returns this OpenAPI 3.0 document as JSON. This endpoint is implemented and live.","operationId":"getOpenApiSpec","x-status":"implemented","responses":{"200":{"description":"OpenAPI specification document","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/status":{"get":{"tags":["meta"],"summary":"Get market ingestion status","description":"Returns sanitized market status derived from the ingestion pipeline. Exposes only safe, non-sensitive fields — no metadata, no error messages, no raw_payload, no secrets. Use `lastSuccessfulIngestionAt` to display accurate 'Last server check' timing in the UI without relying on client-side estimates. Always returns fresh data (no CDN cache).","operationId":"getMarketStatus","x-status":"implemented","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/MarketStatus"}}}}}},"500":{"description":"Internal server error"}}}},"/v1/tokens/prices":{"get":{"tags":["tokens"],"summary":"Get current WoW Token prices","description":"Returns the latest WoW Token price per region for the requested game version, enriched with previousPriceGold, changeGold, changePercent, and enoughHistory. Change fields are null when fewer than two history candles exist for the region. Read from the `latest_token_prices` and `token_price_history_5m` safe views. Always returns fresh data (no CDN cache). Never exposes raw_payload.\n\n`gameVersion` defaults to `retail` for backward compatibility — existing consumers that never sent the parameter continue to receive Retail-only prices. Use `gameVersion=classic` for Classic Progression prices, or `gameVersion=all` for both. Classic is tracked for us/eu/kr/tw only (no CN).","operationId":"getTokenPrices","x-status":"implemented","parameters":[{"name":"gameVersion","in":"query","required":false,"description":"Game version filter. `retail` (default) returns Retail prices only, `classic` returns Classic Progression prices only, `all` returns both.","schema":{"type":"string","enum":["retail","classic","all"],"default":"retail"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/TokenPrice"}}}}}}},"429":{"description":"Rate limited"},"500":{"description":"Internal server error"}}}},"/v1/tokens/history":{"get":{"tags":["tokens"],"summary":"Get WoW Token price history","description":"Returns historical OHLC candles for a region/game version from the `token_price_history_5m` safe view. Supports time-range filtering via the `range` parameter. Only interval=5m is currently implemented; 1h and 1d return 400. Raw tables are not exposed. Never exposes raw_payload. When `range` is specified, the response includes a `meta` object describing data coverage and whether the requested range is fully covered.","operationId":"getTokenHistory","x-status":"implemented","parameters":[{"name":"region","in":"query","required":true,"schema":{"type":"string","enum":["us","eu","kr","tw"]}},{"name":"gameVersion","in":"query","required":false,"schema":{"type":"string","enum":["retail","classic"],"default":"retail"}},{"name":"interval","in":"query","required":false,"description":"Candle interval. Only 5m is currently implemented.","schema":{"type":"string","enum":["5m","1h","1d"],"default":"5m"}},{"name":"range","in":"query","required":false,"description":"Time range to filter candles. `24h` = last 24 hours, `3d` = last 3 days, `7d` = last 7 days, `30d` = last 30 days, `all` = all available history. When specified, the response includes a `meta` object with coverage metadata. If the tracker has not yet collected data covering the full range, `meta.hasEnoughData` will be false and `meta.message` will explain the honest state.","schema":{"type":"string","enum":["24h","3d","7d","30d","all"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of candles to return (most recent first). Defaults to 288 (24h of 5m candles) or the range-specific default when `range` is set. Maximum is 5000.","schema":{"type":"integer","default":288,"minimum":1,"maximum":5000}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TokenPriceHistory"}}}}}},"400":{"description":"Invalid parameters"},"500":{"description":"Internal server error"}}}}},"components":{"schemas":{"MarketStatus":{"type":"object","description":"Sanitized market ingestion status. Contains only safe public fields — no metadata, no error messages, no raw_payload, no secrets.","properties":{"lastSuccessfulIngestionAt":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of the most recent successful ingestion run. Null when no successful run has completed yet. Use this to display accurate 'Last server check Xs ago' in the UI without resetting on page refresh."},"lastIngestionStatus":{"type":"string","enum":["success","partial_success","failed",null],"nullable":true,"description":"Status of the most recent ingestion run."},"cronIntervalSeconds":{"type":"integer","example":60,"description":"How often the cron fires in seconds."},"autoUpdatesEnabled":{"type":"boolean","example":true,"description":"Whether automatic periodic ingestion is active."}}},"TokenPrice":{"type":"object","description":"Latest token price for a region/game version (gold), enriched with previous-price change data. Change fields are null when fewer than two history candles exist for the region.","required":["region","gameVersion","priceGold","fetchedAt","source","enoughHistory"],"properties":{"region":{"type":"string","enum":["us","eu","kr","tw"]},"gameVersion":{"type":"string","enum":["retail","classic"]},"priceGold":{"type":"integer","description":"Token price in gold.","example":250000},"previousPriceGold":{"type":"integer","nullable":true,"description":"Previous distinct token price in gold (close_price of the second-to-last 5m candle). Null when fewer than two candles exist. Never fabricated.","example":249150},"changeGold":{"type":"integer","nullable":true,"description":"priceGold − previousPriceGold. Null when previousPriceGold is null.","example":850},"changePercent":{"type":"number","nullable":true,"description":"(changeGold / previousPriceGold) × 100, rounded to 4 decimal places. Null when previousPriceGold is null.","example":0.34},"enoughHistory":{"type":"boolean","description":"True when at least two candles exist and change values are populated.","example":true},"fetchedAt":{"type":"string","format":"date-time"},"source":{"type":"string","example":"blizzard_api"},"sourceUpdatedAt":{"type":"string","format":"date-time","nullable":true}}},"TokenPriceCandle":{"type":"object","description":"OHLC price candle for a single interval bucket (gold).","required":["bucketStart","openPrice","highPrice","lowPrice","closePrice","avgPrice","sampleCount"],"properties":{"bucketStart":{"type":"string","format":"date-time"},"openPrice":{"type":"integer"},"highPrice":{"type":"integer"},"lowPrice":{"type":"integer"},"closePrice":{"type":"integer"},"avgPrice":{"type":"number"},"sampleCount":{"type":"integer"}}},"TokenPriceHistory":{"type":"object","properties":{"region":{"type":"string","enum":["us","eu","kr","tw"]},"gameVersion":{"type":"string","enum":["retail","classic"]},"interval":{"type":"string","enum":["5m","1h","1d"]},"range":{"type":"string","enum":["24h","3d","7d","30d","all"],"description":"The requested time range, if specified.","nullable":true},"candles":{"type":"array","items":{"$ref":"#/components/schemas/TokenPriceCandle"}},"meta":{"nullable":true,"description":"Coverage metadata, present when `range` was specified.","$ref":"#/components/schemas/HistoryMeta"}}},"HistoryMeta":{"type":"object","description":"Describes the coverage of the returned candle history relative to the requested range. Present only when `range` was specified in the request.","properties":{"range":{"type":"string","enum":["24h","3d","7d","30d","all"]},"requestedRangeLabel":{"type":"string","example":"7 days"},"availableFrom":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of the earliest data point for this region."},"availableTo":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of the most-recent data point for this region."},"availablePointCount":{"type":"integer","description":"Number of candles in this response."},"hasEnoughData":{"type":"boolean","description":"True when available history covers the full requested range. False when the tracker has not yet collected enough history."},"message":{"type":"string","nullable":true,"description":"Human-readable message when hasEnoughData is false, e.g. 'Only ~9h of history collected so far.' Null otherwise."}}}}}}