{
  "openapi": "3.1.0",
  "info": {
    "title": "PublicOptions API",
    "version": "1.2.0",
    "description": "Historical and intraday US options data. Authenticate with a Bearer API key (prefix `sopk_`) generated from your dashboard.\n\n**Versioning.** The current stable version is `v1`. We commit to a 12-month deprecation window for any breaking change. Deprecated endpoints emit `Deprecation`, `Sunset`, and `Link` headers (RFC 9745 / RFC 8594). See https://czar28.com/docs#versioning.\n\n**Idempotency.** Every endpoint accepts an optional `Idempotency-Key` header (≤255 chars). Within 24 hours, retries with the same key and body replay the original response (cached, quota-free, marked with `Idempotent-Replayed: true`). Reusing a key with a different body returns `409 idempotency_conflict`."
  },
  "servers": [
    { "url": "https://czar28.com", "description": "Production" },
    { "url": "https://czar28.com", "description": "Preview (latest staged build)" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Options", "description": "Options market data" },
    { "name": "Health", "description": "Service health" }
  ],
  "paths": {
    "/v1/options/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } }
          }
        }
      }
    },
    "/v1/options/chain": {
      "get": {
        "tags": ["Options"],
        "summary": "List option contracts for a root + expiration",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" },
          { "$ref": "#/components/parameters/Root" },
          { "$ref": "#/components/parameters/Exp" }
        ],
        "responses": {
          "200": {
            "description": "Success",
            
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChainResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/options/quote/eod": {
      "get": {
        "tags": ["Options"],
        "summary": "End-of-day quotes for a single contract",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" },
          { "$ref": "#/components/parameters/Root" },
          { "$ref": "#/components/parameters/Exp" },
          { "$ref": "#/components/parameters/Strike" },
          { "$ref": "#/components/parameters/Right" },
          { "$ref": "#/components/parameters/StartDate" },
          { "$ref": "#/components/parameters/EndDate" }
        ],
        "responses": {
          "200": {
            "description": "Success",
            
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EodResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/options/quote/intraday": {
      "get": {
        "tags": ["Options"],
        "summary": "Intraday quotes for a single contract",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" },
          { "$ref": "#/components/parameters/Root" },
          { "$ref": "#/components/parameters/Exp" },
          { "$ref": "#/components/parameters/Strike" },
          { "$ref": "#/components/parameters/Right" },
          { "$ref": "#/components/parameters/StartDate" },
          { "$ref": "#/components/parameters/EndDate" },
          {
            "name": "ivl",
            "in": "query",
            "description": "Bar interval in milliseconds (1000 – 3600000). Default 60000.",
            "schema": { "type": "integer", "minimum": 1000, "maximum": 3600000, "default": 60000 }
          },
          {
            "name": "rth",
            "in": "query",
            "description": "Regular trading hours only.",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "true" }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntradayResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/options/trades": {
      "get": {
        "tags": ["Options"],
        "summary": "Historical trades for a single contract",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" },
          { "$ref": "#/components/parameters/Root" },
          { "$ref": "#/components/parameters/Exp" },
          { "$ref": "#/components/parameters/Strike" },
          { "$ref": "#/components/parameters/Right" },
          { "$ref": "#/components/parameters/StartDate" },
          { "$ref": "#/components/parameters/EndDate" }
        ],
        "responses": {
          "200": {
            "description": "Success",
            
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TradesResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sopk_xxx",
        "description": "API key from your dashboard. Send as `Authorization: Bearer sopk_...`."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Opaque client-generated string (≤255 chars). Retrying within 24h with the same key and body returns the cached response without consuming additional quota. Reusing the key with a different body returns 409.",
        "schema": { "type": "string", "maxLength": 255, "example": "8e3b5c0a-2f4a-4b9d-9a8e-1c2d3f4a5b6c" }
      },
      "Root": { "name": "root", "in": "query", "required": true, "description": "Underlying ticker (uppercase).", "schema": { "type": "string", "example": "AAPL", "pattern": "^[A-Z0-9.]+$", "maxLength": 8 } },
      "Exp": { "name": "exp", "in": "query", "required": true, "description": "Expiration date YYYYMMDD.", "schema": { "type": "string", "example": "20260619", "pattern": "^\\d{8}$" } },
      "Strike": { "name": "strike", "in": "query", "required": true, "description": "Strike price in dollars.", "schema": { "type": "number", "example": 200 } },
      "Right": { "name": "right", "in": "query", "required": true, "description": "Option right (C=call, P=put).", "schema": { "type": "string", "enum": ["C", "P"] } },
      "StartDate": { "name": "start_date", "in": "query", "required": true, "description": "Start date YYYYMMDD.", "schema": { "type": "string", "example": "20260101", "pattern": "^\\d{8}$" } },
      "EndDate": { "name": "end_date", "in": "query", "required": true, "description": "End date YYYYMMDD.", "schema": { "type": "string", "example": "20260131", "pattern": "^\\d{8}$" } }
    },
    "headers": {
      "RateLimitLimit": { "schema": { "type": "integer" }, "description": "Monthly request quota." },
      "RateLimitRemaining": { "schema": { "type": "integer" }, "description": "Remaining requests this month." },
      "RateLimitReset": { "schema": { "type": "integer" }, "description": "Unix seconds when monthly quota resets." },
      "RateLimitLimitMinute": { "schema": { "type": "integer" }, "description": "Per-minute sustained rate limit." },
      "RateLimitBurst": { "schema": { "type": "integer" }, "description": "Per-minute burst capacity." },
      "RetryAfter": { "schema": { "type": "integer" }, "description": "Seconds until you may retry." },
      "ApiVersion": { "schema": { "type": "string", "example": "v1" }, "description": "API version that served this response." },
      "Deprecation": { "schema": { "type": "string" }, "description": "RFC 9745. Present when the version is deprecated; value is an `@<unix-seconds>` timestamp." },
      "Sunset": { "schema": { "type": "string" }, "description": "RFC 8594. HTTP-date at which this version will be removed (responds 410)." },
      "Link": { "schema": { "type": "string" }, "description": "Link to the successor version, e.g. `<https://.../docs#v2>; rel=\"successor-version\"`." },
      "IdempotentReplayed": { "schema": { "type": "boolean" }, "description": "Present and `true` when this response was served from the idempotency cache." }
    },
    "responses": {
      "Ok": {
        "description": "Success",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Limit-Minute": { "$ref": "#/components/headers/RateLimitLimitMinute" },
          "X-RateLimit-Burst": { "$ref": "#/components/headers/RateLimitBurst" },
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "Deprecation": { "$ref": "#/components/headers/Deprecation" },
          "Sunset": { "$ref": "#/components/headers/Sunset" },
          "Link": { "$ref": "#/components/headers/Link" },
          "Idempotent-Replayed": { "$ref": "#/components/headers/IdempotentReplayed" }
        },
        "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
      },
      "BadRequest": {
        "description": "Invalid parameters",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "IdempotencyConflict": {
        "description": "Idempotency-Key was previously used with a different request body within the 24h window.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Quota exceeded or per-minute rate limited",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" },
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Limit-Minute": { "$ref": "#/components/headers/RateLimitLimitMinute" },
          "X-RateLimit-Burst": { "$ref": "#/components/headers/RateLimitBurst" }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "invalid_parameters" },
          "message": { "type": "string" }
        },
        "required": ["error"]
      },
      "EnvelopeHeader": {
        "type": "object",
        "description": "Metadata about the response. `format` names each column of each row in `response`, in order.",
        "properties": {
          "latency_ms": { "type": "integer", "description": "Server-side latency in milliseconds." },
          "format": { "type": "array", "items": { "type": "string" }, "description": "Column names for each row in `response`, in order." }
        }
      },
      "EodResponse": {
        "type": "object",
        "description": "End-of-day quotes envelope. `response` rows are arrays whose columns are named by `header.format`.",
        "properties": {
          "header": { "$ref": "#/components/schemas/EnvelopeHeader" },
          "response": {
            "type": "array",
            "description": "Each row is an array. Columns appear in the order listed below.",
            "items": { "type": "array" },
            "x-format-fields": [
              { "name": "date", "type": "int (YYYYMMDD)", "description": "Trading date." },
              { "name": "open", "type": "number", "description": "Opening trade price." },
              { "name": "high", "type": "number", "description": "Session high." },
              { "name": "low", "type": "number", "description": "Session low." },
              { "name": "close", "type": "number", "description": "Closing trade price." },
              { "name": "bid", "type": "number", "description": "Closing best bid." },
              { "name": "ask", "type": "number", "description": "Closing best ask." },
              { "name": "volume", "type": "int", "description": "Contracts traded." }
            ]
          }
        }
      },
      "IntradayResponse": {
        "type": "object",
        "description": "Intraday quotes envelope. `response` rows are arrays whose columns are named by `header.format`.",
        "properties": {
          "header": { "$ref": "#/components/schemas/EnvelopeHeader" },
          "response": {
            "type": "array",
            "description": "Each row is an array. Columns appear in the order listed below.",
            "items": { "type": "array" },
            "x-format-fields": [
              { "name": "ms_of_day", "type": "int", "description": "Milliseconds since midnight (US/Eastern)." },
              { "name": "bid", "type": "number", "description": "Best bid at end of interval." },
              { "name": "ask", "type": "number", "description": "Best ask at end of interval." },
              { "name": "bid_size", "type": "int", "description": "Contracts at the best bid." },
              { "name": "ask_size", "type": "int", "description": "Contracts at the best ask." },
              { "name": "date", "type": "int (YYYYMMDD)", "description": "Trading date." }
            ]
          }
        }
      },
      "TradesResponse": {
        "type": "object",
        "description": "Historical trades envelope. `response` rows are arrays whose columns are named by `header.format`.",
        "properties": {
          "header": { "$ref": "#/components/schemas/EnvelopeHeader" },
          "response": {
            "type": "array",
            "description": "Each row is an array. Columns appear in the order listed below.",
            "items": { "type": "array" },
            "x-format-fields": [
              { "name": "ms_of_day", "type": "int", "description": "Milliseconds since midnight (US/Eastern)." },
              { "name": "sequence", "type": "int", "description": "Exchange sequence number." },
              { "name": "size", "type": "int", "description": "Trade size in contracts." },
              { "name": "condition", "type": "int", "description": "OPRA condition code." },
              { "name": "price", "type": "number", "description": "Trade price." },
              { "name": "exchange", "type": "int", "description": "OPRA exchange code." },
              { "name": "date", "type": "int (YYYYMMDD)", "description": "Trading date." }
            ]
          }
        }
      },
      "ChainResponse": {
        "type": "object",
        "description": "Contract list envelope. `response` rows are arrays whose columns are named by `header.format`.",
        "properties": {
          "header": { "$ref": "#/components/schemas/EnvelopeHeader" },
          "response": {
            "type": "array",
            "description": "Each row is an array. Columns appear in the order listed below.",
            "items": { "type": "array" },
            "x-format-fields": [
              { "name": "root", "type": "string", "description": "Underlying ticker." },
              { "name": "expiration", "type": "int (YYYYMMDD)", "description": "Expiration date." },
              { "name": "strike", "type": "int (1/10\u00a2)", "description": "Strike in tenths of a cent. Divide by 1000 for dollars." },
              { "name": "right", "type": "C | P", "description": "Call or put." }
            ]
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["ok", "degraded"], "description": "\"ok\" when healthy, \"degraded\" otherwise." },
          "upstream": {
            "type": "object",
            "properties": {
              "mdds_status": { "type": "string", "enum": ["CONNECTED", "DISCONNECTED", "UNKNOWN"], "description": "Upstream terminal connection state." },
              "upstream_ms": { "type": "integer", "description": "Round-trip latency to upstream in ms." }
            }
          }
        },
        "x-format-fields": [
          { "name": "status", "type": "string", "description": "\"ok\" when healthy, \"degraded\" otherwise." },
          { "name": "upstream.mdds_status", "type": "string", "description": "\"CONNECTED\" | \"DISCONNECTED\" | \"UNKNOWN\"." },
          { "name": "upstream.upstream_ms", "type": "int", "description": "Round-trip latency to upstream in ms." }
        ]
      }
    }
  }
}
