{
  "openapi": "3.1.0",
  "info": {
    "title": "OptionData HTTP APIs",
    "version": "1.0.0",
    "summary": "OPRA-licensed U.S. equity options data — historical SQL, option chain, market structure",
    "description": "OptionData (optiondata.io) provides OPRA-licensed U.S. equity options data.\n\nPro list price $599/month; 14-day free trial.\n\nRealtime trades stream over WebSocket at wss://ws.optiondata.io (see /docs/realtime-option-trades-api/).\n\nHuman docs: /docs/ · AI map: /llms.txt",
    "contact": {
      "name": "OptionData Support",
      "email": "support@optiondata.io",
      "url": "https://www.optiondata.io"
    },
    "license": {
      "name": "Proprietary — OPRA-licensed data product",
      "url": "https://www.optiondata.io/terms-of-service"
    }
  },
  "servers": [
    {
      "url": "https://www.optiondata.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Historical SQL",
      "description": "ClickHouse SELECT over historical option trades"
    },
    {
      "name": "Option Chain",
      "description": "Full option chain snapshots via REST"
    },
    {
      "name": "Market Structure",
      "description": "GEX / dealer-positioning symbol snapshots"
    }
  ],
  "paths": {
    "/api/historical/sql": {
      "post": {
        "tags": [
          "Historical SQL"
        ],
        "summary": "Execute a guarded historical SQL query",
        "description": "Run a SELECT-only ClickHouse query against whitelisted option-trade tables. Trade rows are delayed by at least 15 minutes. Paid subscriptions can query only the past 15 days (rolling execution-time window). Requires an API key. Trial responses are row-capped.",
        "operationId": "historicalSql",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HistoricalSqlRequest"
              },
              "example": {
                "api_key": "apikey_YOUR_KEY",
                "sql": "SELECT symbol, strike, put_call, size, price FROM RawOptionTrades WHERE date = today() AND symbol = 'AAPL' LIMIT 10"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/HistoricalSqlRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Query succeeded",
            "headers": {
              "X-OptionData-Historical-Sql-Minimum-Data-Delay-Minutes": {
                "description": "Minimum age, in minutes, of visible trade rows.",
                "schema": {
                  "type": "integer",
                  "const": 15
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalSqlSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (INVALID_REQUEST) or SQL rejected by guardrails (INVALID_SQL)"
          },
          "401": {
            "description": "Unauthorized (UNAUTHORIZED)"
          },
          "403": {
            "description": "Not entitled (SUBSCRIPTION_REQUIRED)"
          },
          "405": {
            "description": "Method not allowed; use POST (METHOD_NOT_ALLOWED)"
          },
          "422": {
            "description": "Query could not be analyzed (INVALID_QUERY) or exceeded server scan limits (QUERY_TOO_BROAD)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalSqlError"
                },
                "examples": {
                  "invalidQuery": {
                    "summary": "Invalid expression",
                    "value": {
                      "status": "ERROR",
                      "errorCode": "INVALID_QUERY",
                      "reason": "UNKNOWN_IDENTIFIER",
                      "errorMsg": "The query references a column, alias, or table identifier that is not available."
                    }
                  },
                  "queryTooBroad": {
                    "summary": "Query exceeds scan limits",
                    "value": {
                      "status": "ERROR",
                      "errorCode": "QUERY_TOO_BROAD",
                      "reason": "SCAN_LIMIT_EXCEEDED",
                      "hints": [
                        "Narrow the date range.",
                        "Filter by symbol and other selective columns before widening the query.",
                        "Split large requests into smaller time windows.",
                        "Do not automatically retry the same unchanged query.",
                        "Use LIMIT to cap returned rows; LIMIT does not necessarily reduce rows scanned."
                      ],
                      "errorMsg": "Query exceeded the historical-data scan limits. Narrow the query before retrying."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          },
          "500": {
            "description": "Server error"
          },
          "504": {
            "description": "Query timeout"
          }
        }
      }
    },
    "/api/option-chain": {
      "post": {
        "tags": [
          "Option Chain"
        ],
        "summary": "Fetch an option chain for a symbol",
        "description": "Returns call/put contracts across strikes and expirations for a trading date (latest or explicit). The latest session updates about every 90 seconds during U.S. market hours. Included with trialing/active realtime. Keep sustained traffic at one request per second per customer and short bursts to 10 requests or fewer. Throttling is best-effort; honor Retry-After on HTTP 429.",
        "operationId": "optionChain",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptionChainRequest"
              },
              "example": {
                "api_key": "apikey_YOUR_KEY",
                "symbol": "AAPL"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OptionChainRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chain returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OptionChainSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Validation error, including an impossible calendar date"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Not entitled"
          },
          "404": {
            "description": "No chain stored for the date (SNAPSHOT_NOT_FOUND) or for the symbol on that session (SYMBOL_NOT_FOUND). A filter that matches no contract returns 200 with empty data."
          },
          "405": {
            "description": "Method not allowed; use POST (METHOD_NOT_ALLOWED)"
          },
          "422": {
            "description": "Chain too large; narrow the filters (REQUEST_TOO_BROAD)"
          },
          "429": {
            "description": "Rate limited"
          },
          "500": {
            "description": "Server error"
          },
          "504": {
            "description": "Query timeout"
          }
        }
      }
    },
    "/api/v1/market-structure/{symbol}": {
      "get": {
        "tags": [
          "Market Structure"
        ],
        "summary": "Fetch market-structure (GEX) snapshot for a symbol",
        "operationId": "marketStructure",
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "SPY"
            },
            "description": "Underlying option root"
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-07-24"
            },
            "description": "Retained historical snapshot date; omit for active snapshot"
          },
          {
            "name": "Authorization",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Bearer apikey_YOUR_KEY"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarketStructureSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Invalid symbol or date"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Not entitled"
          },
          "404": {
            "description": "Snapshot not found"
          },
          "405": {
            "description": "Method not allowed; use GET (METHOD_NOT_ALLOWED)"
          },
          "429": {
            "description": "Rate limited"
          },
          "500": {
            "description": "Server error"
          },
          "503": {
            "description": "Snapshot service unavailable or timed out"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Bearer token: `Authorization: Bearer <api_key>` where api_key is `cus_...` or `apikey_...`."
      }
    },
    "schemas": {
      "HistoricalSqlRequest": {
        "type": "object",
        "required": [
          "sql"
        ],
        "properties": {
          "api_key": {
            "type": "string",
            "description": "cus_... or apikey_... (optional if session auth is present)"
          },
          "sql": {
            "type": "string",
            "description": "SELECT-only SQL against whitelisted tables (e.g. RawOptionTrades). Visible trade rows are at least 15 minutes old."
          },
          "test_mode": {
            "type": "boolean",
            "description": "When true, returns sample rows without querying ClickHouse"
          }
        }
      },
      "HistoricalSqlSuccess": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "SUCCESS"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "entitlement": {
                "type": "string",
                "enum": [
                  "active",
                  "trialing"
                ]
              },
              "row_limit": {
                "type": "integer",
                "description": "Most rows one response can return."
              },
              "capped": {
                "type": "boolean",
                "description": "True when the trial row cap applies to the plan."
              },
              "truncated": {
                "type": "boolean",
                "description": "True when the query produced more rows than row_limit and the extra rows were dropped."
              },
              "lookback_days": {
                "type": "integer",
                "const": 15,
                "description": "Rolling trade-history window in days; present for active paid subscriptions only."
              },
              "minimum_data_delay_minutes": {
                "type": "integer",
                "const": 15,
                "description": "Minimum age, in minutes, of visible trade rows."
              }
            },
            "additionalProperties": true
          }
        }
      },
      "HistoricalSqlError": {
        "type": "object",
        "required": [
          "status",
          "errorCode",
          "errorMsg"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "ERROR"
          },
          "errorCode": {
            "type": "string",
            "enum": [
              "INVALID_REQUEST",
              "INVALID_SQL",
              "UNAUTHORIZED",
              "SUBSCRIPTION_REQUIRED",
              "METHOD_NOT_ALLOWED",
              "INVALID_QUERY",
              "QUERY_TOO_BROAD",
              "RATE_LIMITED",
              "QUERY_TIMEOUT",
              "INTERNAL_ERROR"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Privacy-safe correction category. INVALID_QUERY uses a query-analysis reason; QUERY_TOO_BROAD uses SCAN_LIMIT_EXCEEDED.",
            "enum": [
              "UNKNOWN_IDENTIFIER",
              "UNKNOWN_FUNCTION",
              "TYPE_MISMATCH",
              "NUMERIC_OVERFLOW",
              "SYNTAX_ERROR",
              "INVALID_AGGREGATION",
              "INVALID_ARGUMENTS",
              "INVALID_EXPRESSION",
              "SCAN_LIMIT_EXCEEDED"
            ]
          },
          "hints": {
            "type": "array",
            "description": "Deterministic recovery guidance returned for QUERY_TOO_BROAD. Hints do not expose database-internal limits or statistics.",
            "items": {
              "type": "string"
            }
          },
          "errorMsg": {
            "type": "string"
          }
        }
      },
      "OptionChainRequest": {
        "type": "object",
        "required": [
          "symbol"
        ],
        "properties": {
          "api_key": {
            "type": "string"
          },
          "symbol": {
            "type": "string",
            "example": "AAPL"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "expiration_date": {
            "type": "string",
            "format": "date"
          },
          "put_call": {
            "type": "string",
            "enum": [
              "CALL",
              "PUT"
            ]
          },
          "strike": {
            "type": "number"
          },
          "strike_min": {
            "type": "number"
          },
          "strike_max": {
            "type": "number"
          }
        }
      },
      "OptionChainSuccess": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "MarketStructureSuccess": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "symbol",
              "symbol_meta",
              "flow",
              "intraday_gex",
              "structure"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "symbol_meta": {
                "type": "object",
                "additionalProperties": true
              },
              "flow": {
                "type": "object",
                "additionalProperties": true
              },
              "intraday_gex": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/MarketStructureIntradayGex"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "structure": {
                "type": "object",
                "additionalProperties": true
              }
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "effective_date",
              "structure_as_of",
              "flow_as_of",
              "intraday_gex_as_of"
            ],
            "properties": {
              "effective_date": {
                "type": "string",
                "format": "date"
              },
              "structure_as_of": {
                "type": "string",
                "format": "date-time"
              },
              "flow_as_of": {
                "type": "string",
                "format": "date-time"
              },
              "intraday_gex_as_of": {
                "oneOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "MarketStructureIntradayGex": {
        "type": "object",
        "required": [
          "spot",
          "call_gex",
          "put_gex"
        ],
        "properties": {
          "spot": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Latest positive underlying price used for the summary"
          },
          "call_gex": {
            "type": "number",
            "minimum": 0,
            "description": "Full-chain positive call dollar GEX"
          },
          "put_gex": {
            "type": "number",
            "maximum": 0,
            "description": "Full-chain signed-negative put dollar GEX"
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Full human-readable API docs",
    "url": "https://www.optiondata.io/docs"
  }
}