{
  "openapi": "3.1.0",
  "info": {
    "title": "NoVo Options Trading — free API",
    "version": "3c944ed9e44d",
    "summary": "Options dealer positioning, volatility percentiles, CFTC positioning and crypto derivatives — keyless.",
    "description": "Every endpoint here is public and free, and works with no credential at all — no account, no OAuth, no key. A free API key exists and is purely optional: it raises the rate ceiling and unlocks nothing. Keyless is the supported path, not a trial.\n\n**Delayed on purpose.** The dealer levels are deliberately delayed and the gamma flip and expected move are withheld from the free tier — the response names them in a `gated` array. A gated field is withheld, not zero and not missing.\n\n**Rate limit.** 600 requests per hour per address, per endpoint. A free API key raises that to 5000/hour and buckets you by key rather than by address — POST an email to `/api/key`. A key is never required and unlocks no additional data; it only raises the ceiling. Exceeding either returns 429 with a `Retry-After` header and a `reason` of `ip_hourly_limit` or `key_hourly_limit`. Branch on `reason`, never on the message text.\n\n**Not described here:** the paid surfaces (live dealer map, crypto map, streamed chart, private desks). They are absent rather than documented-and-refused.\n\nMarket analysis and education. Not financial advice; it does not place trades.",
    "contact": {
      "name": "NoVo Options Trading",
      "url": "https://novo-options.trade"
    },
    "license": {
      "name": "Free to use with attribution to NoVo Options Trading",
      "url": "https://novo-options.trade/terms"
    }
  },
  "servers": [
    {
      "url": "https://novo-options.trade"
    }
  ],
  "paths": {
    "/api/levels": {
      "get": {
        "operationId": "getDealerLevels",
        "tags": [
          "Equities"
        ],
        "summary": "get_dealer_levels",
        "description": "Options dealer positioning for SPY, QQQ and IWM: spot, call wall, put wall and the gamma regime (long or short gamma). DELAYED, and the gamma flip and expected move are withheld from this free feed — the response lists them under `gated`, so do not report them as zero or missing data. The live, undelayed map is the paid NoVo Trader product. TWO SUB-RESOURCES, each returning a DIFFERENT shape: pass `history` with a ticker for that ticker's delayed level history as {points:[...]} — the same delay, so it exposes nothing the live call does not — or `iv` with a ticker for that ticker's IV rank and percentile against its own logged range as {ivRank, ivPct, days}. Pass neither for the normal three-ticker payload, and never both: `history` wins and `iv` is ignored.",
        "parameters": [
          {
            "name": "history",
            "in": "query",
            "required": false,
            "description": "Ticker (SPY, QQQ or IWM) — returns that ticker's delayed level history instead of the normal payload.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "iv",
            "in": "query",
            "required": false,
            "description": "Ticker (SPY, QQQ or IWM) — returns that ticker's IV rank and percentile instead of the normal payload.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/vol": {
      "get": {
        "operationId": "getVolatilityRecord",
        "tags": [
          "Volatility"
        ],
        "summary": "get_volatility_record",
        "description": "VIX, VXN and RVX ranked against their OWN history back to 1990 — last value, percentile against the whole series and against the last two years, min/median/max, plus term structure, VVIX and SKEW. Use this to answer 'is volatility actually high' with a percentile and the window it is measured over instead of an impression.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/market-pulse": {
      "get": {
        "operationId": "getMarketPulse",
        "tags": [
          "Equities"
        ],
        "summary": "get_market_pulse",
        "description": "The NoVo Market Pulse: a 0-100 composite fear/greed score with the factors behind it (volatility, put/call, breadth, momentum) and each factor's weight, so the score can be explained rather than just quoted.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/crypto-free": {
      "get": {
        "operationId": "getCrypto",
        "tags": [
          "Crypto"
        ],
        "summary": "get_crypto_sweep / get_crypto_coin",
        "description": "The free crypto sweep across the mapped coins: price, 24-hour change and a sparkline. The paid Crypto Market Map adds dealer gamma by strike, the block tape and the on-chain liquidity map; none of that is in this response. Per-venue funding, open interest and 24-hour liquidations are FREE per coin — see get_crypto_coin.\n\nOne coin's FREE crypto read, the same data the free coin page serves: per-venue funding (rate + annualized, never blended), per-venue open interest and 24h volume, total OI, 24h long/short liquidations, and the free BTC/ETH gamma summary where the coin has one. The response's `paid` key names what the $79 Crypto Market Map withholds from this feed — gamma by strike, the flip and walls, the block tape, the on-chain map — so never report a gated field as zero or missing; it is withheld, not absent. A coin outside the mapped set answers 200 with covered:false and says what the paid map covers — a non-200 means the request itself failed, never a coverage verdict.",
        "parameters": [
          {
            "name": "coin",
            "in": "query",
            "required": false,
            "description": "Asset code, e.g. BTC, ETH, SOL, DOGE.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/positioning": {
      "get": {
        "operationId": "getFuturesPositioning",
        "tags": [
          "Futures"
        ],
        "summary": "get_futures_positioning",
        "description": "Weekly CFTC Commitments of Traders positioning: speculative and commercial net positions and open interest, with history, for the major index futures. Weekly and lagged by the CFTC's own publication schedule — the `asof` date is the authority, not today.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/calendar": {
      "get": {
        "operationId": "getEconomicCalendar",
        "tags": [
          "Calendar"
        ],
        "summary": "get_economic_calendar",
        "description": "Scheduled US economic releases — the dates and times that reprice volatility.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/heatmap": {
      "get": {
        "operationId": "getSectorHeatmap",
        "tags": [
          "Equities"
        ],
        "summary": "get_sector_heatmap",
        "description": "Today's move by sector, the data behind the free sector heatmap.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/quotes": {
      "get": {
        "operationId": "getQuotes",
        "tags": [
          "Equities"
        ],
        "summary": "get_quotes",
        "description": "Delayed index and ETF quotes for the free ticker strip.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/congress": {
      "get": {
        "operationId": "getCongressTrades",
        "tags": [
          "Congress"
        ],
        "summary": "get_congress_trades",
        "description": "US House stock disclosures from the Clerk's Periodic Transaction Reports. ⚠ THESE ARE DISCLOSURES, NOT TRADES IN REAL TIME: members have up to 45 days to file under the STOCK Act, and amounts are usually a RANGE rather than an exact figure - the response carries its own `lag` and `disclaimer`, and quoting a row without them overstates what it is. No signal is computed and no ranking by 'conviction' is invented; it serves what was filed. Filter with ticker, side (buy/sell) and limit. Also reports `unreadable_filings`, because a filing we could not parse is a gap we name rather than a row we drop silently.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "rows to return (default 100)",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "ticker",
            "in": "query",
            "required": false,
            "description": "filter to one ticker, e.g. NVDA",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "side",
            "in": "query",
            "required": false,
            "description": "buy or sell",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stocks",
            "in": "query",
            "required": false,
            "description": "'0' to include non-stock assets; omit for stocks only",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/market-internals": {
      "get": {
        "operationId": "getShortVolume",
        "tags": [
          "Equities"
        ],
        "summary": "get_short_volume",
        "description": "FINRA off-exchange short volume per ticker, ranked against that ticker's own recent history, plus index-mover earnings, the VIX term structure and options volume. ⚠ READ SHORT VOLUME AS ONE-SIDEDNESS, NOT AS A BEARISH VOTE: most off-exchange short prints are market makers hedging, so a high reading says the tape was one-sided, not that anyone is positioned for a fall. Free, and updated through the session.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/trending": {
      "get": {
        "operationId": "getMovers",
        "tags": [
          "Equities"
        ],
        "summary": "get_movers",
        "description": "Today's biggest movers across a curated universe of recognisable, high-volume names, ranked by absolute percent change. Deliberately NOT a search-spike list: ranking by what people are looking up surfaces obscure tickers and reads as market activity, which is why this ranks on the move itself. Public data, cached about five minutes.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/news": {
      "get": {
        "operationId": "getWireNews",
        "tags": [
          "News"
        ],
        "summary": "get_wire_news",
        "description": "The market news wire NoVo polls, newest first: headline, publisher, timestamp and the tickers each story is tagged to. It reports `symbols_polled` and `more_in_window` so an agent can tell a quiet tape from a truncated read - a short list here is not automatically a quiet market.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "stories to return",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/daily-strike": {
      "get": {
        "operationId": "getDailyStrike",
        "tags": [
          "News"
        ],
        "summary": "get_daily_strike",
        "description": "The Daily Strike: NoVo's published market read. format=json returns the recent stories as data; format=index returns the COMPLETE archive of every published piece with its url, slug and publishedAt, uncapped - use index when you need the whole record rather than the latest few, because json, rss and the news feed are each capped at a different number. Pass a slug with format=json for one specific story. WITHOUT format this path serves a rendered HTML page, not data.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": true,
            "description": "json (recent stories) or index (the complete archive)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "one story's slug, with format=json",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/sentiment": {
      "get": {
        "operationId": "getCrowdSentiment",
        "tags": [
          "Equities"
        ],
        "summary": "get_crowd_sentiment",
        "description": "The public bull/bear poll on novo-options.trade - today's tally, one vote per IP per UTC day. ⚠ THIS IS A READER POLL, NOT A MARKET MEASUREMENT: it counts opinions of people on one site, so it is a crowd reading and nothing more. It is not positioning, it is not flow, and it should never be quoted as either.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/journal-search": {
      "get": {
        "operationId": "searchJournal",
        "tags": [
          "News"
        ],
        "summary": "search_journal",
        "description": "SEARCH EVERYTHING NOVO HAS PUBLISHED - 1,200+ Journal articles on market mechanics plus every page on the site, in one call. Use it to ground an answer in our own writing: 'why does price pin at a strike', 'what is the gamma flip', 'how does funding work'. Returns url, title and description, ranked, with the FULL match count beside the page so a narrow query is distinguishable from a truncated one. ⚠ It matches WORDS, NOT MEANING: if a query returns nothing, retry with the terms an article would actually use rather than concluding the topic is uncovered. Free, no key.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "search terms, 2+ characters",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "results to return, 1-50 (default 10)",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/earnings": {
      "get": {
        "operationId": "getEarningsDates",
        "tags": [
          "Equities"
        ],
        "summary": "get_earnings_dates",
        "description": "The next earnings date for any symbol, from NASDAQ'S OWN PUBLISHED ANALYST CALENDAR. ⚠ THIS IS THIRD-PARTY PUBLIC DATA PASSED THROUGH, NOT A NOVO MEASUREMENT, and the response says so. An upstream failure answers 502 naming the upstream rather than 'no earnings date' - the latter would be reported as 'this company has no earnings scheduled', which is a claim about the company rather than about a fetch. Free, no key.",
        "parameters": [
          {
            "name": "symbol",
            "in": "query",
            "required": true,
            "description": "ticker, e.g. NVDA",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/key": {
      "post": {
        "operationId": "requestApiKey",
        "tags": [
          "Keys"
        ],
        "summary": "Request a free API key",
        "description": "POST an email address and a free key is sent to it. The key is NOT returned in this response by design. A key is never required — it raises the hourly ceiling and unlocks no additional data. Send it as `Authorization: Bearer novo_...` or the header `x-novo-api-key`; a key in a query string is never read.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              },
              "example": {
                "email": "you@company.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted — the key is on its way by email"
          },
          "400": {
            "description": "The address was not accepted"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": [
          "MCP"
        ],
        "summary": "Model Context Protocol endpoint (JSON-RPC 2.0)",
        "description": "Streamable HTTP, stateless. Methods: initialize, ping, tools/list, tools/call, resources/list, prompts/list. Every tool is a read and carries readOnlyHint. GET is not supported on this path and returns 405 — there is no SSE stream.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "RateLimited": {
        "description": "Rate limited — 600/hour per address, or 5000/hour with a free key.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            },
            "description": "Seconds until the window resets."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "ip_hourly_limit",
                    "key_hourly_limit"
                  ]
                },
                "limit": {
                  "type": "integer"
                },
                "retryable": {
                  "type": "boolean"
                },
                "documentation_url": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}