{
  "openapi": "3.1.0",
  "info": {
    "title": "UpCuit OpenAPI",
    "version": "1.0.0",
    "summary": "Free, authless API for live NASDAQ trading halts.",
    "description": "Free, no API key, no sign-up. Returns the current NASDAQ trading-halt snapshot UpCuit tracks, including LUDP volatility pauses and reopening/resumption times.\n\n**Best-effort, no SLA.** Data is derived from NASDAQ's public trading-halt feed and provided as-is, with no uptime, latency, or accuracy guarantee. It is not a system of record and must not be used for automated trading decisions; always confirm a stock's status with your broker or the exchange feed. Responses are CDN-cached for a few seconds, so polling faster than that yields no fresher data. Service is subject to per-IP rate limits (~60 requests/minute) and may change or be withdrawn at any time.\n\n**Compatibility.** Within v1 changes are additive only (new fields, appended enum members) — clients must ignore unknown fields. Any breaking change ships as a new version path. Timestamps are ISO-8601 with offset, alongside an `*_epoch_ms` convenience and a NASDAQ wall-clock (`America/New_York`) fallback; any of these is `null` when the upstream time could not be resolved.\n\n**CORS.** This API does not send `Access-Control-Allow-Origin`, so it cannot be called directly from a third-party web page; use it server-side.",
    "contact": { "name": "UpCuit", "url": "https://upcuit.com/open-api/" }
  },
  "servers": [{ "url": "https://upcuit.com", "description": "Production" }],
  "tags": [{ "name": "halts", "description": "Current NASDAQ trading halts" }, { "name": "meta", "description": "Service health" }],
  "paths": {
    "/api/v1/halts": {
      "get": {
        "tags": ["halts"],
        "operationId": "listHalts",
        "summary": "List current trading halts",
        "description": "Returns the current halt snapshot. Supports conditional GET via `ETag`/`If-None-Match` (304 when unchanged).",
        "parameters": [
          { "name": "symbol", "in": "query", "required": false, "description": "Exact ticker match, case-insensitive.", "schema": { "type": "string" }, "example": "AAPL" },
          { "name": "market", "in": "query", "required": false, "description": "Filter by normalized exchange.", "schema": { "$ref": "#/components/schemas/Market" } },
          { "name": "active", "in": "query", "required": false, "description": "When `true`, return only currently-halted rows (status `halted`).", "schema": { "type": "boolean" } },
          { "name": "reason_code", "in": "query", "required": false, "description": "Match halts carrying this reason code (any reason), case-insensitive.", "schema": { "type": "string" }, "example": "LUDP" }
        ],
        "responses": {
          "200": {
            "description": "Current halts snapshot.",
            "headers": {
              "ETag": { "description": "Opaque tag for the data + filter view; send back as If-None-Match.", "schema": { "type": "string" } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HaltsResponse" } } }
          },
          "304": { "description": "Not modified (the data matched the supplied If-None-Match)." },
          "503": {
            "description": "Feed is warming up — no snapshot is available yet.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          }
        }
      }
    },
    "/api/v1/halts/{symbol}": {
      "get": {
        "tags": ["halts"],
        "operationId": "getHalt",
        "summary": "Get the current halt for one symbol",
        "parameters": [
          { "name": "symbol", "in": "path", "required": true, "description": "Ticker, case-insensitive.", "schema": { "type": "string" }, "example": "AAPL" }
        ],
        "responses": {
          "200": { "description": "The symbol's current halt.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HaltResponse" } } } },
          "404": { "description": "No current trading halt for the symbol.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "503": { "description": "Feed is warming up.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "tags": ["meta"],
        "operationId": "getHealth",
        "summary": "Feed liveness and freshness",
        "responses": {
          "200": { "description": "Feed health.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Health" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Market": { "type": "string", "enum": ["NASDAQ", "NYSE", "AMEX", "BATS", "ARCA", "IEX", "OTHER"] },
      "HaltStatus": {
        "type": "string",
        "enum": ["halted", "quote_resumed", "resumed"],
        "description": "Re-derivable from the timestamps: `resumed` when `resumed_at*` is set, else `quote_resumed` when `quote_resumed_at*` is set, else `halted`."
      },
      "LocalTime": {
        "type": "object",
        "description": "NASDAQ wall-clock fallback (always America/New_York). The only always-present timestamp form.",
        "properties": {
          "date": { "type": "string", "example": "06/11/2026" },
          "time": { "type": "string", "example": "13:30:05" },
          "tz": { "type": "string", "const": "America/New_York" }
        },
        "required": ["date", "time", "tz"]
      },
      "Reason": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "LUDP" },
          "title": { "type": ["string", "null"], "example": "Volatility Trading Pause" }
        },
        "required": ["code", "title"]
      },
      "Halt": {
        "type": "object",
        "properties": {
          "symbol": { "type": "string", "example": "ABCD" },
          "name": { "type": "string", "example": "Example Corp" },
          "market": { "$ref": "#/components/schemas/Market" },
          "status": { "$ref": "#/components/schemas/HaltStatus" },
          "reasons": { "type": "array", "items": { "$ref": "#/components/schemas/Reason" } },
          "pause_threshold_price": { "type": ["number", "null"], "example": 12.34 },
          "halted_at": { "type": ["string", "null"], "format": "date-time", "example": "2026-06-11T13:30:05-04:00" },
          "halted_at_epoch_ms": { "type": ["integer", "null"], "example": 1781112605000 },
          "halted_at_local": { "oneOf": [{ "$ref": "#/components/schemas/LocalTime" }, { "type": "null" }] },
          "resumed_at": { "type": ["string", "null"], "format": "date-time" },
          "resumed_at_epoch_ms": { "type": ["integer", "null"] },
          "resumed_at_local": { "oneOf": [{ "$ref": "#/components/schemas/LocalTime" }, { "type": "null" }] },
          "quote_resumed_at": { "type": ["string", "null"], "format": "date-time" },
          "quote_resumed_at_epoch_ms": { "type": ["integer", "null"] }
        },
        "required": [
          "symbol", "name", "market", "status", "reasons", "pause_threshold_price",
          "halted_at", "halted_at_epoch_ms", "halted_at_local",
          "resumed_at", "resumed_at_epoch_ms", "resumed_at_local",
          "quote_resumed_at", "quote_resumed_at_epoch_ms"
        ]
      },
      "Meta": {
        "type": "object",
        "properties": {
          "generated_at": { "type": "string", "format": "date-time", "description": "When this response was produced." },
          "fetched_at": { "type": ["string", "null"], "format": "date-time", "description": "Last successful upstream fetch." },
          "data_changed_at": { "type": ["string", "null"], "format": "date-time" },
          "data_age_ms": { "type": ["integer", "null"], "description": "Age of the snapshot in milliseconds." },
          "stale": { "type": "boolean", "description": "True when the snapshot is past the freshness threshold (or absent)." },
          "count": { "type": "integer", "description": "Number of halts returned (after filtering)." },
          "source": { "type": "string", "const": "nasdaq" }
        },
        "required": ["generated_at", "fetched_at", "data_changed_at", "data_age_ms", "stale", "count", "source"]
      },
      "HaltsResponse": {
        "type": "object",
        "properties": {
          "api_version": { "type": "integer", "const": 1 },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Halt" } },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["api_version", "data", "meta"]
      },
      "HaltResponse": {
        "type": "object",
        "properties": {
          "api_version": { "type": "integer", "const": 1 },
          "data": { "$ref": "#/components/schemas/Halt" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["api_version", "data", "meta"]
      },
      "Health": {
        "type": "object",
        "properties": {
          "api_version": { "type": "integer", "const": 1 },
          "healthy": { "type": "boolean" },
          "has_snapshot": { "type": "boolean" },
          "data_age_ms": { "type": ["integer", "null"] },
          "last_error_at": { "type": ["string", "null"], "format": "date-time" }
        },
        "required": ["api_version", "healthy", "has_snapshot", "data_age_ms", "last_error_at"]
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details.",
        "properties": {
          "type": { "type": "string", "format": "uri" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" }
        },
        "required": ["type", "title", "status"]
      }
    }
  }
}
