{
  "openapi": "3.1.0",
  "info": {
    "title": "tape — market data API",
    "version": "1.0.0",
    "description": "One REST API over every tick and bar source: Dukascopy tick history, td365 intraday ticks (today), Trade Nation 1m/1h/1d bars and the Trade Nation live feed (WebSocket). One lowercase key per instrument (dax, dow, gold, aluminium …); several names map to it (labels). Every response states which source served which UTC day and at what resolution. All times are UTC epoch milliseconds; dates are UTC days, `from` inclusive and `to` exclusive. Every /api/v1 route needs an API key."
  },
  "servers": [{ "url": "https://tape.brvo.app" }],
  "security": [{ "bearer": [] }],
  "components": {
    "securitySchemes": {
      "bearer": { "type": "http", "scheme": "bearer", "description": "An API key from the admin portal: Authorization: Bearer tape_… (or ?key=tape_… for quick tests)." }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } } },
      "Instrument": {
        "type": "object",
        "properties": {
          "key": { "type": "string", "example": "dax" },
          "name": { "type": "string", "example": "Germany 40" },
          "labels": { "type": "array", "items": { "type": "string" }, "example": ["DAX", "GER40", "DEU.IDX/EUR"] },
          "sources": {
            "type": "object",
            "properties": {
              "dukascopy": { "type": "string", "description": "slug on the Dukascopy archive ('' = none)" },
              "td365": { "type": "string", "description": "Trade Nation market name td365 records today's ticks for" },
              "tn": { "type": "string", "description": "Trade Nation market name for the live feed and 1m/1h/1d bars" },
              "tnFuture": { "type": "boolean", "description": "tn is a futures contract name without its month; the current contract is resolved automatically" },
              "cost": { "type": "string", "description": "id on tradenation.brvo.app (spreads, sessions, margin)" }
            }
          },
          "pointSize": { "type": "number", "description": "price value of one spread point" },
          "decimals": { "type": "integer" },
          "coverage": { "type": "array", "items": { "$ref": "#/components/schemas/Coverage" }, "description": "only on /instruments/{key}" },
          "finest": { "type": "string", "enum": ["tick", "1m", "1h", "1d", "none"], "description": "the finest resolution available anywhere in the history" },
          "costs": { "type": ["object", "null"], "description": "Trade Nation costs: spreads [{spread, from, to}] (Europe/London clock), sessions [{dayIndex (0 = Sunday), durations [{from, to}]}], marginPct" }
        }
      },
      "Coverage": {
        "type": "object",
        "properties": {
          "source": { "type": "string", "enum": ["dukascopy", "td365", "tnbars", "tn"] },
          "kind": { "type": "string", "enum": ["history", "intraday", "live"] },
          "res": { "type": "string", "example": "tick" },
          "from": { "type": "string", "example": "2013-01-01" },
          "to": { "type": "string", "example": "2026-09-23" }
        }
      },
      "Segment": {
        "type": "object",
        "properties": {
          "from": { "type": "string", "format": "date-time" },
          "to": { "type": "string", "format": "date-time" },
          "source": { "type": "string", "enum": ["dukascopy", "td365", "tradenation"] },
          "res": { "type": "string", "description": "the source's own resolution for this range" }
        }
      },
      "Tick": {
        "type": "object",
        "properties": {
          "t": { "type": "integer", "description": "epoch ms (UTC)" },
          "price": { "type": "number", "description": "mid price" },
          "bid": { "type": "number", "description": "price − spread/2, spread from the Trade Nation cost schedule at t" },
          "ask": { "type": "number", "description": "price + spread/2" }
        }
      },
      "Bar": {
        "type": "object",
        "properties": {
          "t": { "type": "integer", "description": "bucket start, epoch ms (UTC)" },
          "o": { "type": "number" }, "h": { "type": "number" }, "l": { "type": "number" }, "c": { "type": "number" },
          "v": { "type": "integer", "description": "tick count (tick sources) or the source's volume" }
        }
      },
      "Data": {
        "type": "object",
        "properties": {
          "key": { "type": "string" },
          "requested": { "type": "string", "description": "the timeframe asked for" },
          "timeframe": { "type": "string", "description": "the timeframe served — coarser than requested only when the data is (e.g. requested 10s, served 1m for an instrument without tick history)" },
          "from": { "type": "string" }, "to": { "type": "string" },
          "segments": { "type": "array", "items": { "$ref": "#/components/schemas/Segment" } },
          "count": { "type": "integer" },
          "ticks": { "type": "array", "items": { "$ref": "#/components/schemas/Tick" }, "description": "present when timeframe = tick" },
          "bars": { "type": "array", "items": { "$ref": "#/components/schemas/Bar" }, "description": "present otherwise" },
          "next": { "type": ["string", "null"], "description": "more data: pass it as ?cursor= with the same from/to. Ticks come one UTC day per page, bars up to 20 000 per page." }
        }
      }
    }
  },
  "paths": {
    "/api/v1/instruments": {
      "get": {
        "summary": "List instruments",
        "parameters": [{ "name": "q", "in": "query", "schema": { "type": "string" }, "description": "filter on key, name and labels" }],
        "responses": { "200": { "description": "the catalogue", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Instrument" } } } } } }
      }
    },
    "/api/v1/instruments/{key}": {
      "get": {
        "summary": "One instrument with coverage, finest resolution and Trade Nation costs",
        "parameters": [{ "name": "key", "in": "path", "required": true, "schema": { "type": "string" }, "description": "key or any label (dax, DAX, GER40)" }],
        "responses": {
          "200": { "description": "instrument", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Instrument" } } } },
          "404": { "description": "unknown", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/instruments/{key}/{from}/{to}": {
      "get": {
        "summary": "Ticks or bars for a UTC date range, stitched across sources",
        "description": "Per UTC day the first source with data wins: dukascopy (tick) → td365 (tick) → Trade Nation 1m → 1h → 1d.",
        "parameters": [
          { "name": "key", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "from", "in": "path", "required": true, "schema": { "type": "string", "format": "date" }, "description": "YYYY-MM-DD, inclusive" },
          { "name": "to", "in": "path", "required": true, "schema": { "type": "string", "format": "date" }, "description": "YYYY-MM-DD, exclusive" },
          { "name": "timeframe", "in": "query", "schema": { "type": "string", "default": "1m" }, "description": "tick, 10s, 30s, 1m, 5m, 15m, 1h, 4h, 1d …" },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "the previous page's `next`" }
        ],
        "responses": {
          "200": { "description": "data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Data" } } } },
          "400": { "description": "bad range or timeframe", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/instruments/{key}/latest": {
      "get": {
        "summary": "Today (UTC) so far",
        "parameters": [
          { "name": "key", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "timeframe", "in": "query", "schema": { "type": "string", "default": "tick" } }
        ],
        "responses": { "200": { "description": "data", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Data" } } } } }
      }
    },
    "/api/v1/search": {
      "get": {
        "summary": "Search: registry, then Dukascopy, then Trade Nation",
        "parameters": [{ "name": "q", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "{query, registry[], dukascopy[], tradenation[], errors, ms} — each hit carries the registry key it is mapped to ('' = unmapped)" } }
      }
    },
    "/api/v1/streams": {
      "get": { "summary": "Health of the live feed (per market) and the cost API", "responses": { "200": { "description": "status" } } }
    },
    "/api/v1/series": {
      "get": { "summary": "Raw single-source catalogue (advanced)", "parameters": [{ "name": "q", "in": "query", "schema": { "type": "string" } }], "responses": { "200": { "description": "entries per source" } } }
    },
    "/api/v1/series/{source}/{key}/{date}": {
      "get": {
        "summary": "Raw single-source day (advanced): exactly what one source has",
        "parameters": [
          { "name": "source", "in": "path", "required": true, "schema": { "type": "string", "enum": ["dukascopy", "scraper"] } },
          { "name": "key", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "date", "in": "path", "required": true, "schema": { "type": "string" }, "description": "YYYY-MM-DD or latest" },
          { "name": "timeframe", "in": "query", "schema": { "type": "string" } },
          { "name": "offset", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "the day" } }
      }
    },
    "/ws": {
      "get": {
        "summary": "Live ticks (WebSocket)",
        "description": "Send {\"auth\":\"<key>\"}, then {\"subscribe\":[\"dax\",\"dow\"]} (or [\"*\"]). Receives {\"type\":\"tick\",\"key\":\"dax\",\"t\":…,\"price\":…,\"bid\":…,\"ask\":…} per tick, pushed the moment Trade Nation sends it. {\"unsubscribe\":[…]} stops a key.",
        "responses": { "101": { "description": "switching protocols" } }
      }
    }
  }
}
