{
  "openapi": "3.1.0",
  "info": {
    "title": "TradingPal pattern API",
    "version": "v1",
    "summary": "Breakout patterns on stocks & crypto, with the historical win rate and a suggested entry, stop and target.",
    "description": "TradingPal helps you find better breakout patterns on stocks & crypto, faster. It looks at the entire market for wedges, pennants and triangles that form right before a large run. Based on research and backtesting, we sift out poorly formed patterns and ones with poor historical results. Each pattern comes with its historical win rate and a suggested entry, stop and target. This API serves what the nightly scan found across the whole market, ranked as the screener ranks it, and the current setups on the tickers an account tracks, with the two lines to draw as dated endpoints (straight on a log-scale price axis), the trigger, stop and target, the track record behind each setup, the last occurrences with outcomes, and a chart image. Nothing is computed per request: every answer is the stored artifact of the last nightly run, and every response says which session it describes in as_of_session, so say that session when presenting the data and do not cache setups across days. Rules for agents: never present a win rate alone. Every track record carries a summary sentence with the sample size, the win rate, the average win and the average loss; quote it whole. This is educational chart analysis, not investment advice; repeat the disclaimer when the user may act on it. Keyed routes send Authorization: Bearer tp_live_\u2026 and work only on the tickers the account tracks; the families, universe, demo and docs routes need no key. Reference: https://api.tradingpal.io/api/v1/llms.txt. MCP server: https://api.tradingpal.io/mcp.",
    "termsOfService": "https://tradingpal.io/terms",
    "contact": {
      "url": "https://tradingpal.io/developers"
    }
  },
  "servers": [
    {
      "url": "https://api.tradingpal.io/api/v1"
    }
  ],
  "tags": [
    {
      "name": "docs",
      "description": "Agent onboarding: the index, the reference (llms.txt), the installable skill and this document. Keyless."
    },
    {
      "name": "account",
      "description": "The calling key, its plan and its limits."
    },
    {
      "name": "scan",
      "description": "What the nightly scan found across the whole market, ranked as the screener ranks it. Needs a key; no ticker needs to be tracked."
    },
    {
      "name": "tickers",
      "description": "The account's tracked tickers, the same list as the site's My watchlist."
    },
    {
      "name": "patterns",
      "description": "Current setups, past occurrences and chart images on any ticker. Need a key."
    },
    {
      "name": "families",
      "description": "The five pattern families with their 20-year track record, and the symbols the nightly covers. Keyless."
    },
    {
      "name": "demo",
      "description": "The keyless demo: real answers for a fixed set of symbols, so an agent can show TradingPal working before its user has a key."
    },
    {
      "name": "keys",
      "description": "Key management for the signed-in site user, authenticated by the site session cookie rather than an API key."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "index",
        "summary": "Where everything is",
        "description": "Returns a JSON map of where everything is: the human docs, llms.txt, skill.md, this OpenAPI document, the MCP server with install commands, the demo symbols, the auth header and the rate limits. Call it first when landing on the base URL with nothing else to go on. No key is needed.",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Links to the docs, the OpenAPI document, llms.txt, the skill, the MCP server and the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Index"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "llmsTxt",
        "summary": "The reference, as markdown for agents",
        "description": "Returns the agent reference as text/markdown with the production URLs filled in. It is the prose contract: the rules for agents, the routes, the envelope, the Setup and StatBlock shapes and the error codes. The same file is served at /llms.txt on the API host. No key is needed; cached for an hour.",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "text/markdown",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/skill.md": {
      "get": {
        "operationId": "skillMd",
        "summary": "The reference as an installable SKILL.md",
        "description": "Returns the same reference wrapped in SKILL.md frontmatter (name and description), so it can be installed as a skill in Claude Code or Codex. The body after the frontmatter is identical to llms.txt. No key is needed; cached for an hour.",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "text/markdown with skill frontmatter",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "openapi",
        "summary": "This document",
        "description": "Returns this document as JSON (OpenAPI 3.1) with the production base URL in servers. Use it to generate a client or a docs page. No key is needed; cached for an hour.",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/me": {
      "get": {
        "operationId": "me",
        "summary": "The key, plan, limits and tracked-ticker count",
        "description": "Returns the calling key (prefix, name, creation time), the plan, the account's limits (the tracked-ticker cap, requests per minute and per day) and how many tickers the account tracks. Call it to confirm a key works and to size a tracking list before PUT /tickers. Needs a key on an account with Premium+.",
        "tags": [
          "account"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/scan": {
      "get": {
        "operationId": "getScan",
        "summary": "What the nightly scan found across the market, ranked",
        "description": "Returns every setup the last nightly run sees across the whole market (stocks & crypto), ranked exactly as the TradingPal screener ranks them: rank 1 is the best-ranked setup of the night. Default status forming returns setups whose trigger has not fired; in_progress returns the ones that triggered and are running; all returns both. Each row is the same Setup object the per-ticker route returns, plus rank and the three numbers the screener shows on each setup: expected_gain_pct (Typical return, the value the ranking sorts by), win_rate_pct with win_rate_trades, and distance_to_trigger_pct (how close the last close is to the trigger). Narrow with family, direction or max_distance_pct, or run your own screen with filters, the same filter list the TradingPal screener takes (price, market cap, volume, RSI, moving averages, performance, win rate, reward to risk, security type, exchange, industry and more); page with limit and offset; count says how many matched. The book changes nightly and the answer is served from a short cache, so page through it within a session and say as_of_session when presenting it. Needs a key on an account with Premium+; no ticker needs to be tracked.",
        "tags": [
          "scan"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Scan"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (family, direction or status not one of the allowed values; max_distance_pct not a number; limit or offset not an integer; filters not a valid predicate list, the message says which predicate)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "family",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "rising_wedge",
                "falling_wedge",
                "bullish_pennant",
                "bearish_pennant",
                "triangle"
              ]
            },
            "description": "Keep one family: rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. Omit for all five. Anything else answers 400 bad_request."
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "bullish",
                "bearish"
              ]
            },
            "description": "Keep one direction. Omit for both."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "forming",
                "in_progress",
                "all"
              ],
              "default": "forming"
            },
            "description": "forming (default): setups whose trigger has not fired. in_progress: setups that triggered and are running. all: both, forming first. Outcomes are never served here."
          },
          {
            "name": "max_distance_pct",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Keep forming setups whose last close is within this many percent of the trigger (absolute distance), for \"about to break out\". In-progress rows are not filtered by it."
          },
          {
            "name": "filters",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "The screener's own filters, URL-encoded: a JSON array of predicates, one per kind: numeric kinds take op (<, <=, >, >=, =) and value, choice and text kinds take value, boolean kinds take value true or false, and the moving-average, performance and high/low kinds also take period. For example filters=[{\"kind\":\"price\",\"op\":\">\",\"value\":10},{\"kind\":\"rsi\",\"op\":\"<\",\"value\":40},{\"kind\":\"asset_type\",\"value\":\"stock\"}]. Every predicate must match; a setup with no evidence for a kind never matches. The kinds, with what each takes and means, are the x-filters list on this operation (the reference page shows it as a table). A malformed list answers 400 bad_request with the reason."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Page size, 1 to 200 (default 50); clamped. count says how many matched."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging."
          },
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "x-mcp-tool": "get_scan",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ],
        "x-filters": [
          {
            "kind": "direction",
            "type": "choice",
            "takes": "value one of bearish, bullish",
            "unit": "",
            "meaning": "The traded direction. Also a plain query parameter.",
            "values": [
              "bearish",
              "bullish"
            ]
          },
          {
            "kind": "phase",
            "type": "choice",
            "takes": "value one of armed, confirmed, forming",
            "unit": "",
            "meaning": "forming, armed (price near the trigger) or confirmed, as the site's screener labels the setup.",
            "values": [
              "armed",
              "confirmed",
              "forming"
            ]
          },
          {
            "kind": "dist_to_entry",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Distance between the last price and the trigger, absolute percent.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "min_win_rate",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Historical win rate of this pattern on this ticker, percent.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "min_avg_r",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "R",
            "meaning": "Average R per past trade on this ticker.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "min_expected_r",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "R",
            "meaning": "Expected R, TradingPal's ticker-plus-family estimate.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "typical_return",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Typical return: the typical winning trade's gain on this ticker and pattern, blended with the pattern's, as the screener shows it.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "min_rr",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "ratio",
            "meaning": "Reward to risk: distance to the target divided by distance to the stop.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "target_move",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Trigger to target, percent.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "pre_pattern_move",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "The run-up before the pattern, percent.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "price",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "$",
            "meaning": "Last price.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "market_cap",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "$",
            "meaning": "Market capitalization.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "avg_volume",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "shares",
            "meaning": "20-day average daily volume.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "dollar_volume",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "$",
            "meaning": "20-day average dollar volume.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "dollar_volume_30d",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "$",
            "meaning": "Price times the 30-day average volume.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "current_dollar_volume",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "$",
            "meaning": "Price times today's volume.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "current_volume",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "shares",
            "meaning": "Today's volume; the day may still be in progress.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "volume_ratio",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "x",
            "meaning": "The last full day's volume over its 20-day average (1 = average).",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "volume_percentile",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "0 to 100",
            "meaning": "Full-day volume rank over the last three months (100 = highest).",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "rsi",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "0 to 100",
            "meaning": "14-day RSI.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "adr",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "14-day average daily range as a percent of price.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "atr",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "14-day average true range, gaps included, as a percent of price.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "volatility",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "20-day realized volatility.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "ema_distance",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Percent above (+) or below (-) the exponential moving average; period 8, 21 or 60.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ],
            "period": "8, 21, 60"
          },
          {
            "kind": "ema_cross",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Percent EMA8 is above EMA21; at least 0 includes equality.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "sma_distance",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Percent above (+) or below (-) the simple moving average; period 20, 50 or 200.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ],
            "period": "20, 50, 200"
          },
          {
            "kind": "price_above_sma",
            "type": "boolean",
            "takes": "value true or false",
            "unit": "true/false",
            "meaning": "true when price is above the simple moving average; period 20, 50 or 200.",
            "period": "20, 50, 200"
          },
          {
            "kind": "sma_cross",
            "type": "boolean",
            "takes": "value true or false",
            "unit": "true/false",
            "meaning": "true when the 50-day average is above the 200-day."
          },
          {
            "kind": "performance",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Price change over the period: 5, 21, 63, 126 or 252 sessions, or ytd.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ],
            "period": "5, 21, 63, 126, 252, ytd"
          },
          {
            "kind": "daily_change",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "One-day price change.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "change_from_open",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Open to last, percent.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "gap",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Opening gap.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ]
          },
          {
            "kind": "distance_from_high",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Percent below the period high; period 20, 50 or 252 sessions, or 1w to 12m.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ],
            "period": "20, 50, 252, or a calendar window (1w, 2w, 4w, 13w, 26w, 52w, 1m, 2m, 3m, 6m, 12m)"
          },
          {
            "kind": "distance_from_low",
            "type": "number",
            "takes": "op (<, <=, >, >=, =) and a number",
            "unit": "%",
            "meaning": "Percent above the period low; period 20, 50 or 252 sessions, or 1w to 12m.",
            "ops": [
              "<",
              "<=",
              "=",
              ">",
              ">="
            ],
            "period": "20, 50, 252, or a calendar window (1w, 2w, 4w, 13w, 26w, 52w, 1m, 2m, 3m, 6m, 12m)"
          },
          {
            "kind": "asset_type",
            "type": "choice",
            "takes": "value one of crypto, etf, stock",
            "unit": "",
            "meaning": "stock, etf or crypto.",
            "values": [
              "crypto",
              "etf",
              "stock"
            ]
          },
          {
            "kind": "exchange",
            "type": "text",
            "takes": "value, a label",
            "unit": "",
            "meaning": "Market identifier code: XNYS, XNAS, ARCX, BATS or XASE."
          },
          {
            "kind": "industry",
            "type": "text",
            "takes": "value, a label",
            "unit": "",
            "meaning": "One of: Semiconductors, Software, Hardware & Electronics, Internet & Media, Telecom, Biotech & Pharma, Healthcare Equipment & Services, Banks, Insurance, Capital Markets & Asset Management, Fintech & Payments, Real Estate, Oil & Gas, Utilities & Renewables, Metals & Mining, Chemicals & Materials, Industrials & Construction, Aerospace & Defense, Transport & Logistics, Retail & E-Commerce, Consumer Goods & Services, Food & Beverage, Restaurants, Travel & Leisure, Autos & Mobility, Crypto, Broad Market & Index, Bonds & Fixed Income, Commodities."
          },
          {
            "kind": "country",
            "type": "text",
            "takes": "value, a label",
            "unit": "",
            "meaning": "US or GLOBAL."
          }
        ]
      }
    },
    "/tickers": {
      "get": {
        "operationId": "listTrackedTickers",
        "summary": "Tracked tickers",
        "description": "Returns the tickers the account tracks, in the order they were added, each with whether the nightly covers it, plus the count, the cap and the account the list belongs to. The tracked set is the account's My watchlist on the TradingPal site, so it also changes when the user edits the watchlist there. The pattern and history routes answer only for these tickers.",
        "tags": [
          "tickers"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tickers"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "x-mcp-tool": "list_tracked_tickers",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateTrackedTickers",
        "summary": "Add or remove tracked tickers",
        "description": "Adds the symbols in `add` to the tracked set and removes the ones in `remove`, leaving the rest alone; removals apply first. Every added symbol must be a ticker TradingPal knows and the result must fit the cap, otherwise nothing changes and the error names the offending symbols; names in `remove` that are not tracked are ignored. Send either list or both. Symbols are upper-cased and duplicates dropped. This also changes the user's My watchlist on the site. The agent tools add_tickers and remove_tickers call this.",
        "tags": [
          "tickers"
        ],
        "responses": {
          "200": {
            "description": "The tracked set as it stands",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tickers"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (neither list sent, or a field is not a list), invalid_symbol (details.symbols), unknown_symbol (details.symbols) or ticker_cap_exceeded (details.cap, details.tracked, details.adding)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The tickers to add and the tickers to remove.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The tickers to add and the tickers to remove; send either or both.",
                "properties": {
                  "add": {
                    "type": "array",
                    "description": "Exchange symbols to start tracking, upper case (BRK.B). Each must be a ticker TradingPal knows; already tracked ones are ignored.",
                    "items": {
                      "type": "string",
                      "description": "Exchange symbol."
                    }
                  },
                  "remove": {
                    "type": "array",
                    "description": "Exchange symbols to stop tracking; names not on the list are ignored.",
                    "items": {
                      "type": "string",
                      "description": "Exchange symbol."
                    }
                  }
                },
                "examples": [
                  {
                    "add": [
                      "NVDA"
                    ],
                    "remove": [
                      "AAPL"
                    ]
                  },
                  {
                    "add": [
                      "NVDA"
                    ]
                  },
                  {
                    "remove": [
                      "AAPL"
                    ]
                  }
                ]
              }
            }
          }
        },
        "x-mcp-tool": [
          "add_tickers",
          "remove_tickers"
        ],
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      },
      "put": {
        "operationId": "setTrackedTickers",
        "summary": "Replace the tracked tickers (all-or-nothing)",
        "description": "Replaces the whole tracked set with the symbols in the body, all or nothing: every symbol must be a ticker TradingPal knows and the list must fit the cap, otherwise nothing changes and the error names the offending symbols. Read GET /tickers first and include the tickers to keep. Symbols are upper-cased and duplicates dropped. This also rewrites the user's My watchlist on the site.",
        "tags": [
          "tickers"
        ],
        "responses": {
          "200": {
            "description": "The new tracked set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tickers"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (symbols is not a list), invalid_symbol (details.symbols), unknown_symbol (details.symbols) or ticker_cap_exceeded (details.cap, details.sent)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The full new tracked list.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The full new tracked list.",
                "properties": {
                  "symbols": {
                    "type": "array",
                    "description": "The complete new list of exchange symbols, upper case (BRK.B). Every symbol must be a ticker TradingPal knows and the list must fit the cap; duplicates are dropped.",
                    "items": {
                      "type": "string",
                      "description": "Exchange symbol."
                    }
                  }
                },
                "required": [
                  "symbols"
                ],
                "examples": [
                  {
                    "symbols": [
                      "NVDA",
                      "AAPL"
                    ]
                  }
                ]
              }
            }
          }
        },
        "x-mcp-tool": "set_tracked_tickers",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/tickers/{symbol}/patterns": {
      "get": {
        "operationId": "getTickerPatterns",
        "summary": "Current setups on a ticker",
        "description": "Returns the setups the last nightly run sees on one ticker, any ticker the nightly covers: family and direction, the two lines as dated endpoints, the trigger rule and price, stop and target, and the family and ticker track records. An empty setups list means nothing is forming; available false means the nightly has no current book for the symbol. The ticker does not need to be tracked. Setups and their ids change nightly, so do not cache them across sessions.",
        "tags": [
          "patterns"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Patterns"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exchange symbol, upper case (BRK.B, not BRK-B). Lower case is accepted and upper-cased."
          },
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "x-mcp-tool": "get_ticker_patterns",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/tickers/{symbol}/history": {
      "get": {
        "operationId": "getPatternHistory",
        "summary": "The last occurrences on a ticker, newest first",
        "description": "Returns the last occurrences of patterns on one ticker with what happened next, newest entry first: entry, stop, target, exit, realized move, R multiple and hold time. This is the receipt behind the ticker track record, not the full ledger: at most 5 rows. outcome narrows the rows to resolved (win or loss) or open trades; total says how many matched before the limit. Per-ticker samples are small, so quote the count beside any rate. The ticker does not need to be tracked.",
        "tags": [
          "patterns"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/History"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (limit is not an integer, or outcome is not all, resolved or open)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exchange symbol, upper case (BRK.B, not BRK-B). Lower case is accepted and upper-cased."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5,
              "default": 5
            },
            "description": "Maximum number of rows to return, 1 to 5 (default 5). History on a ticker is its last occurrences, not the full ledger. Values outside the range are clamped; a non-integer answers 400 bad_request. total says how many rows matched before the limit."
          },
          {
            "name": "outcome",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "resolved",
                "open"
              ],
              "default": "all"
            },
            "description": "Which rows to include: all, resolved (win or loss only) or open (still running). Case-insensitive; any other value answers 400 bad_request."
          },
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "x-mcp-tool": "get_pattern_history",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/tickers/{symbol}/chart.png": {
      "get": {
        "operationId": "getPatternChart",
        "summary": "PNG of one current setup",
        "description": "Returns a PNG of one current setup: candles, both lines, trigger, stop and target. setup_id must come from the patterns route for the same ticker; a setup that is no longer current answers 404 setup_not_found, so re-read the patterns when that happens. The image is rendered once per setup per session and then served from storage; the response is cacheable for an hour. The ticker does not need to be tracked.",
        "tags": [
          "patterns"
        ],
        "responses": {
          "200": {
            "description": "PNG image of the setup: candles, both lines, trigger, stop and target.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (setup_id missing)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "setup_not_found (not a current setup on this symbol) or chart_unavailable (the setup cannot be drawn)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key (no Authorization: Bearer tp_live_\u2026 header) or invalid_api_key (unknown, revoked, or the account is inactive)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (the account is not on Premium+, the plan with API access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exchange symbol, upper case (BRK.B, not BRK-B). Lower case is accepted and upper-cased."
          },
          {
            "name": "setup_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A setup_id from the patterns route for the same symbol (the demo patterns route for demo charts). Setups change nightly; an id that is no longer current answers 404 setup_not_found."
          },
          {
            "name": "X-TradingPal-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional name of the agent or script making the call. It is recorded in the account's usage log so the user can see which client made which calls; the User-Agent is recorded when it is absent."
          }
        ],
        "x-mcp-tool": "get_pattern_chart",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/charts/{token}.png": {
      "get": {
        "operationId": "chartLink",
        "summary": "PNG behind a signed chart link",
        "description": "Returns the PNG a signed chart link points at. Chart links come from the agent tools and nowhere else: `io.tradingpal/chart_url` in the `_meta` of a get_pattern_chart or demo result, which an app card loads as an image because a card cannot send the key. The token names one current setup; the link needs no key, is limited per IP, and stops working when the setup is no longer current or a week after it was issued, whichever comes first.",
        "tags": [
          "patterns"
        ],
        "responses": {
          "200": {
            "description": "PNG image of the setup: candles, both lines, trigger, stop and target.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "chart_link_expired (the link is not valid or has expired) or setup_not_found (the setup is no longer current)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The signed token from a chart link, without the .png suffix."
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/families": {
      "get": {
        "operationId": "listFamilies",
        "summary": "The pattern families with their track record",
        "description": "Returns the five pattern families with their 20-year backtest record. Quote each summary sentence whole; never present win_rate_pct without sample_size and avg_loss_pct. data_version is the manifest the records were sealed in. No key is needed; keyless calls are limited per IP.",
        "tags": [
          "families"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Families"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mcp-tool": "get_family_track_record",
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/families/{family}/stats": {
      "get": {
        "operationId": "getFamilyStats",
        "summary": "One family's track record",
        "description": "Returns one family's 20-year backtest record, the same block GET /families lists for it. An unknown family answers 404 unknown_family with the valid names in fix. No key is needed; keyless calls are limited per IP.",
        "tags": [
          "families"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Family"
                }
              }
            }
          },
          "404": {
            "description": "unknown_family",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "family",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "rising_wedge",
                "falling_wedge",
                "bullish_pennant",
                "bearish_pennant",
                "triangle"
              ]
            },
            "description": "One of the five family identifiers: rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle. Anything else answers 404 unknown_family."
          }
        ],
        "x-mcp-tool": "get_family_track_record",
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/universe": {
      "get": {
        "operationId": "listUniverse",
        "summary": "Symbols the nightly covers",
        "description": "Returns every symbol the active nightly manifest covers, sorted. Use it to check a symbol before tracking it; the unknown_symbol error from PUT /tickers points here. The list is empty when no manifest is active. No key is needed; the answer is cached for an hour and keyless calls are limited per IP.",
        "tags": [
          "families"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Universe"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/demo": {
      "get": {
        "operationId": "demoIndex",
        "summary": "The keyless demo symbols",
        "description": "Returns the demo symbols and URL templates for the demo patterns and chart routes. The demo exists so an agent can show TradingPal working before its user has a key. No key is needed; keyless calls are limited to 30 a minute per IP.",
        "tags": [
          "demo"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DemoIndex"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/demo/{symbol}": {
      "get": {
        "operationId": "demoPatterns",
        "summary": "Current setups on a demo symbol, no key needed",
        "description": "Returns the current setups on one demo symbol, exactly as the keyed patterns route would, plus demo: true and a next sentence on getting a key. Only the demo symbols answer; any other symbol is 404 not_a_demo_symbol with the demo set in details.demo_symbols. No key is needed; keyless calls are limited per IP. The demo MCP tool is the same call with the first setup's chart attached as an image.",
        "tags": [
          "demo"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Patterns"
                }
              }
            }
          },
          "404": {
            "description": "not_a_demo_symbol (details.demo_symbols lists the set)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exchange symbol, upper case (BRK.B, not BRK-B). Lower case is accepted and upper-cased."
          }
        ],
        "x-mcp-tool": "demo",
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/demo/{symbol}/chart.png": {
      "get": {
        "operationId": "demoChart",
        "summary": "PNG of one demo setup",
        "description": "Returns the PNG of one current setup on a demo symbol, as the keyed chart route would. setup_id comes from the demo patterns route; symbols outside the demo set answer 404 not_a_demo_symbol. No key is needed; keyless calls are limited per IP. The demo MCP tool returns the first setup's chart inline instead of taking a setup_id.",
        "tags": [
          "demo"
        ],
        "responses": {
          "200": {
            "description": "PNG image of the setup: candles, both lines, trigger, stop and target.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (setup_id missing)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_a_demo_symbol, setup_not_found or chart_unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; wait details.retry_after_seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exchange symbol, upper case (BRK.B, not BRK-B). Lower case is accepted and upper-cased."
          },
          {
            "name": "setup_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A setup_id from the patterns route for the same symbol (the demo patterns route for demo charts). Setups change nightly; an id that is no longer current answers 404 setup_not_found."
          }
        ],
        "x-mcp-tool": "demo",
        "security": [
          {},
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "patterns"
            ]
          }
        ]
      }
    },
    "/keys": {
      "get": {
        "operationId": "listKeys",
        "summary": "The signed-in site user's keys (session cookie, not a key)",
        "description": "Lists the signed-in site user's active keys, newest first, without their secrets. This route is authenticated by the site's session cookie, not by an API key; it serves the developers page. Revoked keys are not listed.",
        "tags": [
          "keys"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Keys"
                }
              }
            }
          },
          "403": {
            "description": "not signed in to the site",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The default Django REST framework error body.",
                  "properties": {
                    "detail": {
                      "type": "string",
                      "description": "What went wrong, in one sentence."
                    }
                  },
                  "examples": [
                    {
                      "detail": "Authentication credentials were not provided."
                    }
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "SessionCookie": []
          }
        ]
      },
      "post": {
        "operationId": "createKey",
        "summary": "Create a key; the full key is returned once",
        "description": "Creates an API key for the signed-in site user and returns it once, in key; only its hash is stored, so it cannot be shown again. name is optional and defaults to \"default\". Session cookie auth, not an API key.",
        "tags": [
          "keys"
        ],
        "responses": {
          "201": {
            "description": "Created; key holds the full secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Key"
                }
              }
            }
          },
          "403": {
            "description": "not signed in to the site",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The default Django REST framework error body.",
                  "properties": {
                    "detail": {
                      "type": "string",
                      "description": "What went wrong, in one sentence."
                    }
                  },
                  "examples": [
                    {
                      "detail": "Authentication credentials were not provided."
                    }
                  ]
                }
              }
            }
          }
        },
        "requestBody": {
          "description": "An optional label for the key.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "An optional label for the key.",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "A label for the key, for example the agent or script that will use it, at most 64 characters. Defaults to \"default\".",
                    "maxLength": 64
                  }
                },
                "examples": [
                  {
                    "name": "my-agent"
                  }
                ]
              }
            }
          }
        },
        "security": [
          {
            "SessionCookie": []
          }
        ]
      }
    },
    "/keys/{key_id}": {
      "delete": {
        "operationId": "revokeKey",
        "summary": "Revoke a key",
        "description": "Revokes one of the signed-in user's keys; requests that carry it answer 401 invalid_api_key from then on. Cannot be undone. Answers 404 when the id is not one of the user's active keys. Session cookie auth, not an API key.",
        "tags": [
          "keys"
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevoked"
                }
              }
            }
          },
          "403": {
            "description": "not signed in to the site",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The default Django REST framework error body.",
                  "properties": {
                    "detail": {
                      "type": "string",
                      "description": "What went wrong, in one sentence."
                    }
                  },
                  "examples": [
                    {
                      "detail": "Authentication credentials were not provided."
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such key: not one of the user's active keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The default Django REST framework error body.",
                  "properties": {
                    "detail": {
                      "type": "string",
                      "description": "What went wrong, in one sentence."
                    }
                  },
                  "examples": [
                    {
                      "detail": "Authentication credentials were not provided."
                    }
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The numeric id of the key, from GET /keys."
          }
        ],
        "security": [
          {
            "SessionCookie": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tp_live_\u2026",
        "description": "An API key in the Authorization header: \"Authorization: Bearer tp_live_\u2026\". Create one at https://tradingpal.io/developers. Limits are 60 requests a minute and 10,000 a day per key. Keyed routes answer only for accounts on Premium+ (which includes Premium); the fix on a 403 says where to get it (https://tradingpal.io/developers/quickstart)."
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "The sign-in flow MCP hosts run (OAuth 2.1 with PKCE and dynamic client registration): the host opens the browser, the user approves, and the grant is a key named after the host on the keys page. The access token goes in the same Authorization header. Metadata at https://api.tradingpal.io/.well-known/oauth-authorization-server.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.tradingpal.io/oauth/authorize",
            "tokenUrl": "https://api.tradingpal.io/oauth/token",
            "refreshUrl": "https://api.tradingpal.io/oauth/token",
            "scopes": {
              "patterns": "Everything the account's key can do: read setups, history, records and charts, and change tracked tickers."
            }
          }
        }
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "tp_sessionid",
        "description": "The TradingPal site session cookie, for the key-management routes only; they are used by the developers page, not by API clients."
      }
    },
    "schemas": {
      "StatBlock": {
        "type": [
          "object",
          "null"
        ],
        "title": "StatBlock",
        "description": "A track record: how one pattern family resolved over the last 20 years of the served universe, or how it resolved on one ticker. Null where a record would go when none is stored; never a partial block. Never present win_rate_pct without sample_size and avg_loss_pct; quote summary whole.",
        "properties": {
          "scope": {
            "type": "string",
            "description": "family when the record covers every ticker in the served universe, ticker when it covers one symbol.",
            "enum": [
              "family",
              "ticker"
            ]
          },
          "family": {
            "type": "string",
            "description": "The pattern family the record is for (rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle)."
          },
          "symbol": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ticker at ticker scope; null at family scope."
          },
          "win_rate_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of resolved trades that reached the target, as a percent (54.0 means 54%), one decimal. At ticker scope the stored rate is shrunk toward the family rate so a handful of trades cannot read as 0% or 100%; wins and losses stay raw. Never quote it without sample_size and avg_loss_pct."
          },
          "sample_size": {
            "type": "integer",
            "description": "Number of trades in the record: the stored trade count, or wins plus losses when no count is stored. The summary sentence quotes it as resolved trades."
          },
          "wins": {
            "type": "integer",
            "description": "Resolved trades that reached the target."
          },
          "losses": {
            "type": "integer",
            "description": "Resolved trades that hit the stop. A bar that reaches both target and stop counts as a loss."
          },
          "avg_win_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean price move of the winning trades from entry to exit, as a percent of the entry price (positive), two decimals."
          },
          "avg_loss_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean price move of the losing trades from entry to exit, as a percent of the entry price (negative), two decimals."
          },
          "median_win_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Median winning move as a percent of the entry price: the typical win, less sensitive to outliers than avg_win_pct."
          },
          "avg_trade_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean price move over all resolved trades, wins and losses together, as a percent of the entry price."
          },
          "avg_r": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean R multiple over resolved trades, where one R is the entry-to-stop distance; 0.41 means the average trade made 0.41 times its risk."
          },
          "expected_r": {
            "type": [
              "number",
              "null"
            ],
            "description": "Per-trade expectancy in R: win rate times the average winning R plus loss rate times the average losing R. Equals avg_r when both come from the same trades."
          },
          "profit_factor": {
            "type": [
              "number",
              "null"
            ],
            "description": "Gross R of the winning trades divided by gross R of the losing trades; above 1 means the wins outweigh the losses. Null when the record has no losses."
          },
          "avg_hold_bars": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean number of daily bars (sessions) from entry to exit over resolved trades, one decimal."
          },
          "backtest_range": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "description": "An ISO date.",
              "format": "date"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "The window the trades were taken from, as [first date, last date] in ISO format, at most the last 20 years. Null when the record carries no dates."
          },
          "measured": {
            "type": "string",
            "description": "The measurement rule in one paragraph: how entries, stops, targets and exits are counted. Identical on every block."
          },
          "summary": {
            "type": "string",
            "description": "One sentence with the sample size, the window, the win rate, the average win, the average loss and the median win. Quote it whole rather than picking numbers out of it."
          }
        },
        "examples": [
          {
            "scope": "family",
            "family": "falling_wedge",
            "symbol": null,
            "win_rate_pct": 54.0,
            "sample_size": 1287,
            "wins": 695,
            "losses": 592,
            "avg_win_pct": 8.1,
            "avg_loss_pct": -4.2,
            "median_win_pct": 6.3,
            "avg_trade_pct": 2.4,
            "avg_r": 0.41,
            "expected_r": 0.41,
            "profit_factor": 1.61,
            "avg_hold_bars": 11.2,
            "backtest_range": [
              "2006-09-25",
              "2026-09-25"
            ],
            "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
            "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
          }
        ]
      },
      "Setup": {
        "type": "object",
        "title": "Setup",
        "description": "One current pattern on a ticker: the lines to draw, the trigger, stop and target, and the track record behind them. Everything is read from the last nightly run; nothing is computed per request.",
        "properties": {
          "setup_id": {
            "type": "string",
            "description": "Identifier of this pattern occurrence in the current nightly book. Pass it as setup_id to the chart.png route. It is stable while the setup stays current and is not valid across nights, so re-read the patterns when the chart route answers setup_not_found."
          },
          "lineage_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Key of the candidate lineage this occurrence belongs to, shared by the revisions of the same detected pattern. Null when the stored candidate carries none."
          },
          "page_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link that opens this setup on the TradingPal site: the Screener quick view, focused on the ticker, naming this exact setup. Free to read. Give it to the user when the chat app cannot show the chart image."
          },
          "symbol": {
            "type": "string",
            "description": "Exchange symbol, upper case."
          },
          "interval": {
            "type": "string",
            "description": "Bar interval. Always D: daily bars.",
            "const": "D"
          },
          "family": {
            "type": "string",
            "description": "The display family of the pattern, one of rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle.",
            "enum": [
              "rising_wedge",
              "falling_wedge",
              "bullish_pennant",
              "bearish_pennant",
              "triangle"
            ]
          },
          "family_label": {
            "type": "string",
            "description": "Human-readable family name, for example \"Falling wedge\"."
          },
          "pattern_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The detector's own pattern type, which can be more specific than family (for example a triangle subtype). Null when the artifact carries none."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "description": "The side the pattern is expected to resolve to: bullish or bearish. Null when the pattern has no single direction, such as a symmetrical triangle that carries a plan for each side.",
            "enum": [
              "bullish",
              "bearish",
              null
            ]
          },
          "status": {
            "type": "string",
            "description": "Lifecycle stage of the setup: forming until the trigger rule fires, in_progress once a daily close has triggered it (entered is then present). Defaults to forming when the artifact stores no stage."
          },
          "pattern_start": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the first bar of the pattern, as an ISO date. Null when unknown.",
            "format": "date"
          },
          "pattern_end": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the last bar of the detected pattern, as an ISO date; the lines run on past it to the last session. Null when unknown.",
            "format": "date"
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The session this setup is valid for, as an ISO date; the same value as the envelope's as_of_session.",
            "format": "date"
          },
          "last_close": {
            "type": [
              "number",
              "null"
            ],
            "description": "The close of the last session before as_of_session, in dollars, so a caller can place the trigger relative to price without another quote; every route reports the same value for the same setup. Null when no price is stored."
          },
          "price_scale": {
            "type": "string",
            "description": "Always log: the lines are straight in log price, so draw them on a log-scale price axis.",
            "const": "log"
          },
          "lines": {
            "type": "object",
            "description": "The two boundary lines of the pattern.",
            "properties": {
              "upper": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The upper boundary. Bullish plans trigger on a daily close above it. Null when the stored pattern has no upper line.",
                "properties": {
                  "start": {
                    "type": "object",
                    "description": "The earlier endpoint: the session and price where the line begins.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "end": {
                    "type": "object",
                    "description": "The later endpoint, at the end of the pattern geometry. The trigger price sits on the extension of the line past this point.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "touch_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "How many bars touched the line, as the detector counted them; the length of touches when no count is stored."
                  },
                  "touches": {
                    "type": "array",
                    "description": "The bars that touched the line, in the order the detector recorded them, so a charting tool can reproduce the fit. May be empty.",
                    "items": {
                      "type": "object",
                      "description": "One touch: the session date and the bar high (upper line) or bar low (lower line).",
                      "properties": {
                        "date": {
                          "type": "string",
                          "description": "Session date of the touching bar, as an ISO date.",
                          "format": "date"
                        },
                        "price": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The bar high (upper line) or low (lower line) in dollars. Null when that bar is not in the stored candle tail."
                        }
                      },
                      "required": [
                        "date",
                        "price"
                      ]
                    }
                  }
                }
              },
              "lower": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The lower boundary. Bearish plans trigger on a daily close below it. Null when the stored pattern has no lower line.",
                "properties": {
                  "start": {
                    "type": "object",
                    "description": "The earlier endpoint: the session and price where the line begins.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "end": {
                    "type": "object",
                    "description": "The later endpoint, at the end of the pattern geometry. The trigger price sits on the extension of the line past this point.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "touch_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "How many bars touched the line, as the detector counted them; the length of touches when no count is stored."
                  },
                  "touches": {
                    "type": "array",
                    "description": "The bars that touched the line, in the order the detector recorded them, so a charting tool can reproduce the fit. May be empty.",
                    "items": {
                      "type": "object",
                      "description": "One touch: the session date and the bar high (upper line) or bar low (lower line).",
                      "properties": {
                        "date": {
                          "type": "string",
                          "description": "Session date of the touching bar, as an ISO date.",
                          "format": "date"
                        },
                        "price": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The bar high (upper line) or low (lower line) in dollars. Null when that bar is not in the stored candle tail."
                        }
                      },
                      "required": [
                        "date",
                        "price"
                      ]
                    }
                  }
                }
              }
            }
          },
          "trigger_rule": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trigger rule in one sentence, chosen by direction: a daily close above the upper line, below the lower line, or through either line when the pattern carries a plan per side. Always present."
          },
          "plans": {
            "type": "array",
            "description": "One trade plan per direction the pattern can resolve in: usually one, two for a symmetrical triangle. Empty when the stored candidate has no eligible branch.",
            "items": {
              "type": "object",
              "description": "The trade plan for one direction: the close that confirms the pattern, the stop and the target.",
              "properties": {
                "direction": {
                  "type": "string",
                  "description": "The side this plan trades: bullish (a close above the upper line) or bearish (a close below the lower line).",
                  "enum": [
                    "bullish",
                    "bearish"
                  ]
                },
                "trigger_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The daily close that confirms the pattern, in dollars, as of the last session. It sits on the boundary line, so it moves a little each session. Null when the stored branch has none."
                },
                "stop_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The stop level in dollars. The backtest freezes it at entry and fills it on a touch, or at the open after an adverse gap. Null when the stored branch has none."
                },
                "target_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The measured-move target in dollars: the pattern height projected from the trigger. Null when the stored branch has none."
                }
              }
            }
          },
          "track_record": {
            "type": "object",
            "description": "The backtest records behind this setup, at family scope and at ticker scope.",
            "properties": {
              "family": {
                "$ref": "#/components/schemas/StatBlock",
                "description": "The family's record over the served universe. Null when no record is sealed for the family."
              },
              "ticker": {
                "$ref": "#/components/schemas/StatBlock",
                "description": "The record of this family on this ticker. Null when the ticker has no resolved history for the family, or too few trades to display."
              }
            }
          },
          "entered": {
            "type": "object",
            "description": "The fill, present only once the trigger has fired (status in_progress). Absent while forming.",
            "properties": {
              "entry_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Session date of the bar that triggered the setup, as an ISO date. Null when the artifact stores no entry time.",
                "format": "date"
              },
              "entry_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The entry price in dollars, recorded when the trigger fired."
              },
              "stop_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The stop in dollars, frozen at entry."
              },
              "target_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The target in dollars, fixed at entry."
              }
            }
          }
        },
        "required": [
          "setup_id",
          "symbol",
          "family",
          "status",
          "lines",
          "plans",
          "track_record"
        ],
        "examples": [
          {
            "setup_id": "NVDA:D:falling_wedge:1",
            "lineage_id": "lineage-1",
            "page_url": "https://www.tradingpal.io/screener/falling-wedge?interval=D&focus=NVDA&p=aB3",
            "symbol": "NVDA",
            "interval": "D",
            "family": "falling_wedge",
            "family_label": "Falling wedge",
            "pattern_type": "falling_wedge",
            "direction": "bullish",
            "status": "forming",
            "pattern_start": "2026-08-08",
            "pattern_end": "2026-09-07",
            "as_of_session": "2026-09-25",
            "last_close": 101.5,
            "price_scale": "log",
            "lines": {
              "upper": {
                "start": {
                  "date": "2026-08-08",
                  "price": 110.0
                },
                "end": {
                  "date": "2026-09-07",
                  "price": 104.0
                },
                "touch_count": 3,
                "touches": [
                  {
                    "date": "2026-08-08",
                    "price": 111.2
                  }
                ]
              },
              "lower": {
                "start": {
                  "date": "2026-08-08",
                  "price": 90.0
                },
                "end": {
                  "date": "2026-09-07",
                  "price": 98.0
                },
                "touch_count": 2,
                "touches": []
              }
            },
            "trigger_rule": "A daily close above the upper line; the trigger price moves with the line.",
            "plans": [
              {
                "direction": "bullish",
                "trigger_price": 105.25,
                "stop_price": 96.0,
                "target_price": 120.0
              }
            ],
            "track_record": {
              "family": {
                "scope": "family",
                "family": "falling_wedge",
                "symbol": null,
                "win_rate_pct": 54.0,
                "sample_size": 1287,
                "wins": 695,
                "losses": 592,
                "avg_win_pct": 8.1,
                "avg_loss_pct": -4.2,
                "median_win_pct": 6.3,
                "avg_trade_pct": 2.4,
                "avg_r": 0.41,
                "expected_r": 0.41,
                "profit_factor": 1.61,
                "avg_hold_bars": 11.2,
                "backtest_range": [
                  "2006-09-25",
                  "2026-09-25"
                ],
                "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
                "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
              },
              "ticker": null
            }
          }
        ]
      },
      "ScanSetup": {
        "type": "object",
        "title": "ScanSetup",
        "description": "One row of the nightly scan: the same Setup object the per-ticker route returns, plus its place in the ranking.",
        "properties": {
          "rank": {
            "type": "integer",
            "description": "Position in the screener's ranking for this status on this session: 1 is the best-ranked forming setup of the night (or the first in-progress trade). Ranks are assigned over the whole book before any filter, so rank 7 stays rank 7 when the request narrows by family."
          },
          "expected_gain_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Typical return, as the TradingPal screener labels it, and the value the ranking sorts by: the typical winning move for this pattern on this ticker in percent, blended with the family's record (ticker evidence weighted by its trade count). Null when the setup is not ranked; unranked rows sort last."
          },
          "win_rate_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "The win rate the TradingPal screener shows on this setup, in percent: the share of past trades of this pattern that reached the target. Null without a track record."
          },
          "win_rate_trades": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many past trades win_rate_pct counts. Null without a track record."
          },
          "distance_to_trigger_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "How far the last close is from the trigger price, in percent of the close, signed: positive when price must rise to reach the trigger. Small is \"about to break out\". Null without a last close or plan."
          },
          "setup_id": {
            "type": "string",
            "description": "Identifier of this pattern occurrence in the current nightly book. Pass it as setup_id to the chart.png route. It is stable while the setup stays current and is not valid across nights, so re-read the patterns when the chart route answers setup_not_found."
          },
          "lineage_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Key of the candidate lineage this occurrence belongs to, shared by the revisions of the same detected pattern. Null when the stored candidate carries none."
          },
          "page_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link that opens this setup on the TradingPal site: the Screener quick view, focused on the ticker, naming this exact setup. Free to read. Give it to the user when the chat app cannot show the chart image."
          },
          "symbol": {
            "type": "string",
            "description": "Exchange symbol, upper case."
          },
          "interval": {
            "type": "string",
            "description": "Bar interval. Always D: daily bars.",
            "const": "D"
          },
          "family": {
            "type": "string",
            "description": "The display family of the pattern, one of rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle.",
            "enum": [
              "rising_wedge",
              "falling_wedge",
              "bullish_pennant",
              "bearish_pennant",
              "triangle"
            ]
          },
          "family_label": {
            "type": "string",
            "description": "Human-readable family name, for example \"Falling wedge\"."
          },
          "pattern_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The detector's own pattern type, which can be more specific than family (for example a triangle subtype). Null when the artifact carries none."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "description": "The side the pattern is expected to resolve to: bullish or bearish. Null when the pattern has no single direction, such as a symmetrical triangle that carries a plan for each side.",
            "enum": [
              "bullish",
              "bearish",
              null
            ]
          },
          "status": {
            "type": "string",
            "description": "Lifecycle stage of the setup: forming until the trigger rule fires, in_progress once a daily close has triggered it (entered is then present). Defaults to forming when the artifact stores no stage."
          },
          "pattern_start": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the first bar of the pattern, as an ISO date. Null when unknown.",
            "format": "date"
          },
          "pattern_end": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the last bar of the detected pattern, as an ISO date; the lines run on past it to the last session. Null when unknown.",
            "format": "date"
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The session this setup is valid for, as an ISO date; the same value as the envelope's as_of_session.",
            "format": "date"
          },
          "last_close": {
            "type": [
              "number",
              "null"
            ],
            "description": "The close of the last session before as_of_session, in dollars, so a caller can place the trigger relative to price without another quote; every route reports the same value for the same setup. Null when no price is stored."
          },
          "price_scale": {
            "type": "string",
            "description": "Always log: the lines are straight in log price, so draw them on a log-scale price axis.",
            "const": "log"
          },
          "lines": {
            "type": "object",
            "description": "The two boundary lines of the pattern.",
            "properties": {
              "upper": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The upper boundary. Bullish plans trigger on a daily close above it. Null when the stored pattern has no upper line.",
                "properties": {
                  "start": {
                    "type": "object",
                    "description": "The earlier endpoint: the session and price where the line begins.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "end": {
                    "type": "object",
                    "description": "The later endpoint, at the end of the pattern geometry. The trigger price sits on the extension of the line past this point.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "touch_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "How many bars touched the line, as the detector counted them; the length of touches when no count is stored."
                  },
                  "touches": {
                    "type": "array",
                    "description": "The bars that touched the line, in the order the detector recorded them, so a charting tool can reproduce the fit. May be empty.",
                    "items": {
                      "type": "object",
                      "description": "One touch: the session date and the bar high (upper line) or bar low (lower line).",
                      "properties": {
                        "date": {
                          "type": "string",
                          "description": "Session date of the touching bar, as an ISO date.",
                          "format": "date"
                        },
                        "price": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The bar high (upper line) or low (lower line) in dollars. Null when that bar is not in the stored candle tail."
                        }
                      },
                      "required": [
                        "date",
                        "price"
                      ]
                    }
                  }
                }
              },
              "lower": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The lower boundary. Bearish plans trigger on a daily close below it. Null when the stored pattern has no lower line.",
                "properties": {
                  "start": {
                    "type": "object",
                    "description": "The earlier endpoint: the session and price where the line begins.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "end": {
                    "type": "object",
                    "description": "The later endpoint, at the end of the pattern geometry. The trigger price sits on the extension of the line past this point.",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Session date of the bar, as an ISO date.",
                        "format": "date"
                      },
                      "price": {
                        "type": "number",
                        "description": "Price in dollars, rounded to four decimals."
                      }
                    },
                    "required": [
                      "date",
                      "price"
                    ]
                  },
                  "touch_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "How many bars touched the line, as the detector counted them; the length of touches when no count is stored."
                  },
                  "touches": {
                    "type": "array",
                    "description": "The bars that touched the line, in the order the detector recorded them, so a charting tool can reproduce the fit. May be empty.",
                    "items": {
                      "type": "object",
                      "description": "One touch: the session date and the bar high (upper line) or bar low (lower line).",
                      "properties": {
                        "date": {
                          "type": "string",
                          "description": "Session date of the touching bar, as an ISO date.",
                          "format": "date"
                        },
                        "price": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The bar high (upper line) or low (lower line) in dollars. Null when that bar is not in the stored candle tail."
                        }
                      },
                      "required": [
                        "date",
                        "price"
                      ]
                    }
                  }
                }
              }
            }
          },
          "trigger_rule": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trigger rule in one sentence, chosen by direction: a daily close above the upper line, below the lower line, or through either line when the pattern carries a plan per side. Always present."
          },
          "plans": {
            "type": "array",
            "description": "One trade plan per direction the pattern can resolve in: usually one, two for a symmetrical triangle. Empty when the stored candidate has no eligible branch.",
            "items": {
              "type": "object",
              "description": "The trade plan for one direction: the close that confirms the pattern, the stop and the target.",
              "properties": {
                "direction": {
                  "type": "string",
                  "description": "The side this plan trades: bullish (a close above the upper line) or bearish (a close below the lower line).",
                  "enum": [
                    "bullish",
                    "bearish"
                  ]
                },
                "trigger_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The daily close that confirms the pattern, in dollars, as of the last session. It sits on the boundary line, so it moves a little each session. Null when the stored branch has none."
                },
                "stop_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The stop level in dollars. The backtest freezes it at entry and fills it on a touch, or at the open after an adverse gap. Null when the stored branch has none."
                },
                "target_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The measured-move target in dollars: the pattern height projected from the trigger. Null when the stored branch has none."
                }
              }
            }
          },
          "track_record": {
            "type": "object",
            "description": "The backtest records behind this setup, at family scope and at ticker scope.",
            "properties": {
              "family": {
                "$ref": "#/components/schemas/StatBlock",
                "description": "The family's record over the served universe. Null when no record is sealed for the family."
              },
              "ticker": {
                "$ref": "#/components/schemas/StatBlock",
                "description": "The record of this family on this ticker. Null when the ticker has no resolved history for the family, or too few trades to display."
              }
            }
          },
          "entered": {
            "type": "object",
            "description": "The fill, present only once the trigger has fired (status in_progress). Absent while forming.",
            "properties": {
              "entry_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Session date of the bar that triggered the setup, as an ISO date. Null when the artifact stores no entry time.",
                "format": "date"
              },
              "entry_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The entry price in dollars, recorded when the trigger fired."
              },
              "stop_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The stop in dollars, frozen at entry."
              },
              "target_price": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The target in dollars, fixed at entry."
              }
            }
          }
        },
        "required": [
          "rank",
          "setup_id",
          "symbol",
          "family",
          "status",
          "lines",
          "plans",
          "track_record"
        ],
        "examples": [
          {
            "rank": 1,
            "expected_gain_pct": 6.8,
            "win_rate_pct": 54.0,
            "win_rate_trades": 31,
            "distance_to_trigger_pct": 3.69,
            "setup_id": "NVDA:D:falling_wedge:1",
            "lineage_id": "lineage-1",
            "page_url": "https://www.tradingpal.io/screener/falling-wedge?interval=D&focus=NVDA&p=aB3",
            "symbol": "NVDA",
            "interval": "D",
            "family": "falling_wedge",
            "family_label": "Falling wedge",
            "pattern_type": "falling_wedge",
            "direction": "bullish",
            "status": "forming",
            "pattern_start": "2026-08-08",
            "pattern_end": "2026-09-07",
            "as_of_session": "2026-09-25",
            "last_close": 101.5,
            "price_scale": "log",
            "lines": {
              "upper": {
                "start": {
                  "date": "2026-08-08",
                  "price": 110.0
                },
                "end": {
                  "date": "2026-09-07",
                  "price": 104.0
                },
                "touch_count": 3,
                "touches": [
                  {
                    "date": "2026-08-08",
                    "price": 111.2
                  }
                ]
              },
              "lower": {
                "start": {
                  "date": "2026-08-08",
                  "price": 90.0
                },
                "end": {
                  "date": "2026-09-07",
                  "price": 98.0
                },
                "touch_count": 2,
                "touches": []
              }
            },
            "trigger_rule": "A daily close above the upper line; the trigger price moves with the line.",
            "plans": [
              {
                "direction": "bullish",
                "trigger_price": 105.25,
                "stop_price": 96.0,
                "target_price": 120.0
              }
            ],
            "track_record": {
              "family": {
                "scope": "family",
                "family": "falling_wedge",
                "symbol": null,
                "win_rate_pct": 54.0,
                "sample_size": 1287,
                "wins": 695,
                "losses": 592,
                "avg_win_pct": 8.1,
                "avg_loss_pct": -4.2,
                "median_win_pct": 6.3,
                "avg_trade_pct": 2.4,
                "avg_r": 0.41,
                "expected_r": 0.41,
                "profit_factor": 1.61,
                "avg_hold_bars": 11.2,
                "backtest_range": [
                  "2006-09-25",
                  "2026-09-25"
                ],
                "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
                "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
              },
              "ticker": null
            }
          }
        ]
      },
      "HistoryRow": {
        "type": "object",
        "title": "HistoryRow",
        "description": "One past occurrence of a pattern on a ticker and what happened next, from the stored backtest: entry, stop, target, exit and result.",
        "properties": {
          "setup_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the occurrence in the stored history. Null when the row carries none. It is not necessarily a setup_id the patterns route still serves."
          },
          "lineage_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the setup lineage: the connected set of breakout attempts of one pattern. attempt says where this row sits in it. Null when absent."
          },
          "family": {
            "type": "string",
            "description": "The display family the row is filed under, derived from pattern_type: wedges keep their name, every triangle type is triangle, squeeze consolidations are bullish_pennant or bearish_pennant by direction, and anything else is other."
          },
          "family_label": {
            "type": "string",
            "description": "Human-readable family name."
          },
          "pattern_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The detector's pattern type for the row. Null when absent."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "description": "bullish or bearish. Null when the row carries neither a direction nor a bias."
          },
          "outcome": {
            "type": "string",
            "description": "How the trade ended: win (target reached), loss (stop hit, or target and stop in the same bar) or open (still running as of the last session).",
            "enum": [
              "win",
              "loss",
              "open"
            ]
          },
          "entry_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the entry bar, as an ISO date. Null when absent.",
            "format": "date"
          },
          "entry_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Entry price in dollars: the close of the breakout bar."
          },
          "stop_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "The stop in dollars, frozen at entry."
          },
          "target_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "The measured-move target in dollars."
          },
          "exit_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session date of the exit bar, as an ISO date. Null for open trades.",
            "format": "date"
          },
          "exit_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Exit price in dollars. Null for open trades."
          },
          "realized_move_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Signed price move from entry to exit as a percent of the entry price, positive when in the trade's favour. Null when no exit is recorded."
          },
          "target_move_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "The pattern's measured move as a percent of price: the size of the move the target was projected from. Null when the row carries none."
          },
          "r_multiple": {
            "type": [
              "number",
              "null"
            ],
            "description": "Realized result in R: exit minus entry, divided by the entry-to-stop distance, signed by direction. Null for open trades."
          },
          "unrealized_r": {
            "type": [
              "number",
              "null"
            ],
            "description": "For open trades only, the R at the last close walked. Null on resolved rows."
          },
          "hold_bars": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Daily bars (sessions) from entry to exit. Null for open trades."
          },
          "attempt": {
            "type": "object",
            "description": "Where this row sits among the breakout attempts of its lineage.",
            "properties": {
              "number": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The 1-based attempt number within the lineage. The history lists each lineage's first attempt, so this is 1 on served rows. Null when the row has no lineage data."
              },
              "count": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "How many breakout attempts the lineage made in total. Null when the row has no lineage data."
              }
            }
          }
        },
        "examples": [
          {
            "setup_id": "NVDA:D:falling_wedge:500",
            "lineage_id": "l500",
            "family": "falling_wedge",
            "family_label": "Falling wedge",
            "pattern_type": "falling_wedge",
            "direction": "bullish",
            "outcome": "win",
            "entry_date": "2026-05-04",
            "entry_price": 100.0,
            "stop_price": 95.0,
            "target_price": 108.0,
            "exit_date": "2026-05-20",
            "exit_price": 108.0,
            "realized_move_pct": 8.0,
            "target_move_pct": 8.0,
            "r_multiple": 1.6,
            "unrealized_r": null,
            "hold_bars": 12,
            "attempt": {
              "number": 1,
              "count": 1
            }
          }
        ]
      },
      "Scan": {
        "type": "object",
        "title": "Scan",
        "description": "What the last nightly scan found across the whole market, ranked.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "available": {
            "type": "boolean",
            "description": "True when the nightly has a current book. False when no book exists for the session; setups is then empty."
          },
          "status": {
            "type": "string",
            "description": "The status filter applied: forming, in_progress or all.",
            "enum": [
              "forming",
              "in_progress",
              "all"
            ]
          },
          "filters": {
            "type": "object",
            "description": "The other filters as applied, null where none was sent.",
            "properties": {
              "family": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The family filter."
              },
              "direction": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The direction filter."
              },
              "max_distance_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The distance cap."
              },
              "predicates": {
                "type": "array",
                "items": {
                  "type": "object"
                },
                "description": "The screener predicates as applied, in canonical form and order; empty when none was sent."
              }
            }
          },
          "count": {
            "type": "integer",
            "description": "How many setups matched the filters, before paging."
          },
          "limit": {
            "type": "integer",
            "description": "The page size applied (1 to 200)."
          },
          "offset": {
            "type": "integer",
            "description": "The offset applied."
          },
          "setups": {
            "type": "array",
            "description": "This page of the ranked setups, best rank first.",
            "items": {
              "$ref": "#/components/schemas/ScanSetup",
              "description": "One ranked setup."
            }
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer",
          "available",
          "status",
          "count",
          "limit",
          "offset",
          "setups"
        ],
        "examples": [
          {
            "api_version": "v1",
            "as_of_session": "2026-09-25",
            "available": true,
            "status": "forming",
            "filters": {
              "family": null,
              "direction": null,
              "max_distance_pct": null,
              "predicates": []
            },
            "count": 212,
            "limit": 50,
            "offset": 0,
            "setups": [
              {
                "rank": 1,
                "expected_gain_pct": 6.8,
                "win_rate_pct": 54.0,
                "win_rate_trades": 31,
                "distance_to_trigger_pct": 3.69,
                "setup_id": "NVDA:D:falling_wedge:1",
                "lineage_id": "lineage-1",
                "page_url": "https://www.tradingpal.io/screener/falling-wedge?interval=D&focus=NVDA&p=aB3",
                "symbol": "NVDA",
                "interval": "D",
                "family": "falling_wedge",
                "family_label": "Falling wedge",
                "pattern_type": "falling_wedge",
                "direction": "bullish",
                "status": "forming",
                "pattern_start": "2026-08-08",
                "pattern_end": "2026-09-07",
                "as_of_session": "2026-09-25",
                "last_close": 101.5,
                "price_scale": "log",
                "lines": {
                  "upper": {
                    "start": {
                      "date": "2026-08-08",
                      "price": 110.0
                    },
                    "end": {
                      "date": "2026-09-07",
                      "price": 104.0
                    },
                    "touch_count": 3,
                    "touches": [
                      {
                        "date": "2026-08-08",
                        "price": 111.2
                      }
                    ]
                  },
                  "lower": {
                    "start": {
                      "date": "2026-08-08",
                      "price": 90.0
                    },
                    "end": {
                      "date": "2026-09-07",
                      "price": 98.0
                    },
                    "touch_count": 2,
                    "touches": []
                  }
                },
                "trigger_rule": "A daily close above the upper line; the trigger price moves with the line.",
                "plans": [
                  {
                    "direction": "bullish",
                    "trigger_price": 105.25,
                    "stop_price": 96.0,
                    "target_price": 120.0
                  }
                ],
                "track_record": {
                  "family": {
                    "scope": "family",
                    "family": "falling_wedge",
                    "symbol": null,
                    "win_rate_pct": 54.0,
                    "sample_size": 1287,
                    "wins": 695,
                    "losses": 592,
                    "avg_win_pct": 8.1,
                    "avg_loss_pct": -4.2,
                    "median_win_pct": 6.3,
                    "avg_trade_pct": 2.4,
                    "avg_r": 0.41,
                    "expected_r": 0.41,
                    "profit_factor": 1.61,
                    "avg_hold_bars": 11.2,
                    "backtest_range": [
                      "2006-09-25",
                      "2026-09-25"
                    ],
                    "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
                    "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
                  },
                  "ticker": null
                }
              }
            ]
          }
        ]
      },
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Every non-2xx answer. The code is stable; message and fix are written so an agent can act on them.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable code, for example setup_not_found. The llms.txt reference lists every code with its HTTP status."
          },
          "message": {
            "type": "string",
            "description": "What went wrong, in one sentence."
          },
          "fix": {
            "type": "string",
            "description": "What to do next: the route or tool to call, or the input to change. Absent when there is nothing to do."
          },
          "docs_url": {
            "type": "string",
            "description": "The developer docs, where keys are created.",
            "format": "uri"
          },
          "details": {
            "type": "object",
            "description": "Code-specific extras: retry_after_seconds on rate_limited, symbols on invalid_symbol and unknown_symbol, cap and sent on ticker_cap_exceeded, demo_symbols on not_a_demo_symbol. Absent otherwise.",
            "properties": {},
            "additionalProperties": true
          }
        },
        "required": [
          "error",
          "message",
          "docs_url"
        ],
        "examples": [
          {
            "error": "setup_not_found",
            "message": "no current setup NVDA:D:falling_wedge:500 on NVDA",
            "fix": "Setups change nightly; re-read the current patterns for the symbol.",
            "docs_url": "https://tradingpal.io/developers"
          }
        ]
      },
      "Me": {
        "type": "object",
        "title": "Me",
        "description": "The calling key, the plan, the account's limits and how many tickers it tracks.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "key": {
            "type": "object",
            "description": "The key that made the call, without its secret.",
            "properties": {
              "prefix": {
                "type": "string",
                "description": "The first 16 characters of the key (tp_live_ plus eight)."
              },
              "name": {
                "type": "string",
                "description": "The label given when the key was created."
              },
              "created_at": {
                "type": "string",
                "description": "When the key was created, ISO 8601 date-time.",
                "format": "date-time"
              }
            }
          },
          "plan": {
            "type": "string",
            "description": "The account's plan: api (Premium+, which includes Premium) or staff. No other account passes the gate.",
            "enum": [
              "api",
              "staff"
            ]
          },
          "limits": {
            "type": "object",
            "description": "The account's limits. Every value is an integer.",
            "properties": {
              "tracked_tickers": {
                "type": "integer",
                "description": "How many tickers the account may track: the cap PUT /tickers enforces."
              },
              "requests_per_minute": {
                "type": "integer",
                "description": "Per-key burst limit, requests per minute (60)."
              },
              "requests_per_day": {
                "type": "integer",
                "description": "Per-key daily limit, requests per day (10,000)."
              }
            },
            "additionalProperties": {
              "type": "integer",
              "description": "Any further limit, as an integer."
            }
          },
          "tracked_ticker_count": {
            "type": "integer",
            "description": "How many tickers the account tracks now."
          },
          "docs_url": {
            "type": "string",
            "description": "The developer docs, where keys are managed.",
            "format": "uri"
          },
          "mcp_url": {
            "type": "string",
            "description": "The MCP server endpoint (Streamable HTTP), which takes the same key.",
            "format": "uri"
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "plan": "api",
            "tracked_ticker_count": 3
          }
        ]
      },
      "Tickers": {
        "type": "object",
        "title": "Tickers",
        "description": "The account's tracked tickers: the account's My watchlist on the TradingPal site.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "tickers": {
            "type": "array",
            "description": "The tracked tickers, in the order they were added.",
            "items": {
              "type": "object",
              "description": "One tracked ticker.",
              "properties": {
                "symbol": {
                  "type": "string",
                  "description": "Exchange symbol, upper case."
                },
                "covered": {
                  "type": "boolean",
                  "description": "True when the active nightly manifest covers the symbol, so the pattern and history routes can answer for it. False means it is tracked but the nightly has no data for it."
                }
              }
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of tracked tickers."
          },
          "cap": {
            "type": "integer",
            "description": "Maximum number of tickers the account may track."
          },
          "account": {
            "type": "object",
            "description": "The TradingPal account the list belongs to, so a host can say which account is connected when the list is not the one the user expected.",
            "properties": {
              "email": {
                "type": "string",
                "description": "The account's sign-in email."
              }
            }
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "tickers": [
              {
                "symbol": "NVDA",
                "covered": true
              }
            ],
            "count": 1,
            "cap": 200,
            "account": {
              "email": "you@example.com"
            }
          }
        ]
      },
      "Patterns": {
        "type": "object",
        "title": "Patterns",
        "description": "The current setups on one symbol, from the last nightly run.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "symbol": {
            "type": "string",
            "description": "The symbol asked for, upper-cased."
          },
          "available": {
            "type": "boolean",
            "description": "True when the nightly has a current candidate book for the symbol. False when the symbol is excluded from pattern analysis or no current book exists; setups is then empty."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why available is false, when the reason is known: not_covered means the symbol is excluded from pattern analysis. Absent when available is true, and when the nightly simply has no current book for the symbol."
          },
          "setups": {
            "type": "array",
            "description": "The current setups, one per eligible candidate. Empty when nothing is forming.",
            "items": {
              "$ref": "#/components/schemas/Setup",
              "description": "One current setup."
            }
          },
          "stock_facts": {
            "type": [
              "object",
              "null"
            ],
            "description": "The ticker's Stock facts as the TradingPal screener's quick view shows them: price and moves, performance, momentum (EMAs, RSI, moving averages), volume and volatility, and the company. Ticker facts, so they come once per answer, not per setup. Null when the ticker has no current facts.",
            "properties": {
              "as_of": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "When the facts were read, for example \"Facts as of the Oct 6 close.\""
              },
              "sections": {
                "type": "array",
                "description": "Groups in the quick view's order: Price, Performance, Momentum, Volume and volatility, and Company (Asset for crypto). A missing fact is left out and an empty group dropped.",
                "items": {
                  "type": "object",
                  "description": "One group.",
                  "properties": {
                    "title": {
                      "type": "string",
                      "description": "The group's heading."
                    },
                    "rows": {
                      "type": "array",
                      "description": "The group's facts, in order.",
                      "items": {
                        "type": "object",
                        "description": "One fact: the label the screener uses and the value as it shows it.",
                        "properties": {
                          "label": {
                            "type": "string",
                            "description": "For example \"EMA8 vs EMA21\" or \"RSI (14)\"."
                          },
                          "value": {
                            "type": "string",
                            "description": "Ready to show, units included, for example \"+2.5%\", \"$43B\" or \"59\"."
                          },
                          "tone": {
                            "type": "string",
                            "description": "up or down on signed price moves; absent on other facts.",
                            "enum": [
                              "up",
                              "down"
                            ]
                          },
                          "change": {
                            "type": "string",
                            "description": "Only on the Last row: the day's change, for example \"+0.40%\"."
                          },
                          "change_tone": {
                            "type": "string",
                            "description": "up or down for change.",
                            "enum": [
                              "up",
                              "down"
                            ]
                          }
                        },
                        "required": [
                          "label",
                          "value"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "demo": {
            "type": "boolean",
            "description": "True on the demo routes. Absent on the keyed route."
          },
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "On the demo routes, a sentence saying where to get a key for other tickers. Absent on the keyed route."
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer",
          "symbol",
          "available",
          "setups"
        ],
        "examples": [
          {
            "api_version": "v1",
            "as_of_session": "2026-09-25",
            "symbol": "NVDA",
            "available": true,
            "setups": [
              {
                "setup_id": "NVDA:D:falling_wedge:1",
                "lineage_id": "lineage-1",
                "page_url": "https://www.tradingpal.io/screener/falling-wedge?interval=D&focus=NVDA&p=aB3",
                "symbol": "NVDA",
                "interval": "D",
                "family": "falling_wedge",
                "family_label": "Falling wedge",
                "pattern_type": "falling_wedge",
                "direction": "bullish",
                "status": "forming",
                "pattern_start": "2026-08-08",
                "pattern_end": "2026-09-07",
                "as_of_session": "2026-09-25",
                "last_close": 101.5,
                "price_scale": "log",
                "lines": {
                  "upper": {
                    "start": {
                      "date": "2026-08-08",
                      "price": 110.0
                    },
                    "end": {
                      "date": "2026-09-07",
                      "price": 104.0
                    },
                    "touch_count": 3,
                    "touches": [
                      {
                        "date": "2026-08-08",
                        "price": 111.2
                      }
                    ]
                  },
                  "lower": {
                    "start": {
                      "date": "2026-08-08",
                      "price": 90.0
                    },
                    "end": {
                      "date": "2026-09-07",
                      "price": 98.0
                    },
                    "touch_count": 2,
                    "touches": []
                  }
                },
                "trigger_rule": "A daily close above the upper line; the trigger price moves with the line.",
                "plans": [
                  {
                    "direction": "bullish",
                    "trigger_price": 105.25,
                    "stop_price": 96.0,
                    "target_price": 120.0
                  }
                ],
                "track_record": {
                  "family": {
                    "scope": "family",
                    "family": "falling_wedge",
                    "symbol": null,
                    "win_rate_pct": 54.0,
                    "sample_size": 1287,
                    "wins": 695,
                    "losses": 592,
                    "avg_win_pct": 8.1,
                    "avg_loss_pct": -4.2,
                    "median_win_pct": 6.3,
                    "avg_trade_pct": 2.4,
                    "avg_r": 0.41,
                    "expected_r": 0.41,
                    "profit_factor": 1.61,
                    "avg_hold_bars": 11.2,
                    "backtest_range": [
                      "2006-09-25",
                      "2026-09-25"
                    ],
                    "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
                    "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
                  },
                  "ticker": null
                }
              }
            ],
            "stock_facts": {
              "as_of": "Facts as of the Sep 25 close.",
              "sections": [
                {
                  "title": "Price",
                  "rows": [
                    {
                      "label": "Last",
                      "value": "$101.50",
                      "change": "+0.40%",
                      "change_tone": "up"
                    },
                    {
                      "label": "Below 52-week high",
                      "value": "6.5%"
                    }
                  ]
                },
                {
                  "title": "Performance",
                  "rows": [
                    {
                      "label": "1W",
                      "value": "+6.2%",
                      "tone": "up"
                    },
                    {
                      "label": "1M",
                      "value": "-3.5%",
                      "tone": "down"
                    }
                  ]
                },
                {
                  "title": "Momentum",
                  "rows": [
                    {
                      "label": "vs EMA8",
                      "value": "+1.2%"
                    },
                    {
                      "label": "EMA8 vs EMA21",
                      "value": "+2.5%"
                    },
                    {
                      "label": "RSI (14)",
                      "value": "59"
                    }
                  ]
                },
                {
                  "title": "Volume and volatility",
                  "rows": [
                    {
                      "label": "Relative volume",
                      "value": "72nd percentile \u00b7 last 3 months"
                    },
                    {
                      "label": "Avg volume (20d)",
                      "value": "2.5M \u00b7 $381M/day"
                    }
                  ]
                },
                {
                  "title": "Company",
                  "rows": [
                    {
                      "label": "Market cap",
                      "value": "$3.2T"
                    },
                    {
                      "label": "Exchange \u00b7 Country",
                      "value": "Nasdaq \u00b7 US"
                    },
                    {
                      "label": "Type",
                      "value": "Stock"
                    }
                  ]
                }
              ]
            }
          }
        ]
      },
      "History": {
        "type": "object",
        "title": "History",
        "description": "Past occurrences of patterns on one symbol with what happened next.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "symbol": {
            "type": "string",
            "description": "The symbol asked for, upper-cased."
          },
          "available": {
            "type": "boolean",
            "description": "True when a current-schema pattern history is stored for the symbol. False when none is; occurrences is then empty and total is 0."
          },
          "occurrences": {
            "type": "array",
            "description": "The rows, newest entry first, after the outcome filter and the limit.",
            "items": {
              "$ref": "#/components/schemas/HistoryRow",
              "description": "One past occurrence."
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of rows in occurrences."
          },
          "total": {
            "type": "integer",
            "description": "Number of rows matching the outcome filter before the limit was applied."
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "symbol": "NVDA",
            "available": true,
            "occurrences": [
              {
                "setup_id": "NVDA:D:falling_wedge:500",
                "lineage_id": "l500",
                "family": "falling_wedge",
                "family_label": "Falling wedge",
                "pattern_type": "falling_wedge",
                "direction": "bullish",
                "outcome": "win",
                "entry_date": "2026-05-04",
                "entry_price": 100.0,
                "stop_price": 95.0,
                "target_price": 108.0,
                "exit_date": "2026-05-20",
                "exit_price": 108.0,
                "realized_move_pct": 8.0,
                "target_move_pct": 8.0,
                "r_multiple": 1.6,
                "unrealized_r": null,
                "hold_bars": 12,
                "attempt": {
                  "number": 1,
                  "count": 1
                }
              }
            ],
            "count": 1,
            "total": 1
          }
        ]
      },
      "Families": {
        "type": "object",
        "title": "Families",
        "description": "Every pattern family with its 20-year record.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "families": {
            "type": "array",
            "description": "One entry per family, in the fixed order rising_wedge, falling_wedge, bullish_pennant, bearish_pennant, triangle.",
            "items": {
              "type": "object",
              "description": "One pattern family with its record.",
              "properties": {
                "family": {
                  "type": "string",
                  "description": "Family identifier.",
                  "enum": [
                    "rising_wedge",
                    "falling_wedge",
                    "bullish_pennant",
                    "bearish_pennant",
                    "triangle"
                  ]
                },
                "label": {
                  "type": "string",
                  "description": "Human-readable family name."
                },
                "track_record": {
                  "$ref": "#/components/schemas/StatBlock",
                  "description": "The family's 20-year record. Null when no record is sealed in the active manifest."
                }
              }
            }
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "families": [
              {
                "family": "falling_wedge",
                "label": "Falling wedge",
                "track_record": {
                  "scope": "family",
                  "family": "falling_wedge",
                  "symbol": null,
                  "win_rate_pct": 54.0,
                  "sample_size": 1287,
                  "wins": 695,
                  "losses": 592,
                  "avg_win_pct": 8.1,
                  "avg_loss_pct": -4.2,
                  "median_win_pct": 6.3,
                  "avg_trade_pct": 2.4,
                  "avg_r": 0.41,
                  "expected_r": 0.41,
                  "profit_factor": 1.61,
                  "avg_hold_bars": 11.2,
                  "backtest_range": [
                    "2006-09-25",
                    "2026-09-25"
                  ],
                  "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
                  "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
                }
              }
            ]
          }
        ]
      },
      "Family": {
        "type": "object",
        "title": "Family",
        "description": "One pattern family with its 20-year record.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "family": {
            "type": "string",
            "description": "Family identifier.",
            "enum": [
              "rising_wedge",
              "falling_wedge",
              "bullish_pennant",
              "bearish_pennant",
              "triangle"
            ]
          },
          "label": {
            "type": "string",
            "description": "Human-readable family name."
          },
          "track_record": {
            "$ref": "#/components/schemas/StatBlock",
            "description": "The family's 20-year record. Null when no record is sealed in the active manifest."
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "family": "falling_wedge",
            "label": "Falling wedge",
            "track_record": {
              "scope": "family",
              "family": "falling_wedge",
              "symbol": null,
              "win_rate_pct": 54.0,
              "sample_size": 1287,
              "wins": 695,
              "losses": 592,
              "avg_win_pct": 8.1,
              "avg_loss_pct": -4.2,
              "median_win_pct": 6.3,
              "avg_trade_pct": 2.4,
              "avg_r": 0.41,
              "expected_r": 0.41,
              "profit_factor": 1.61,
              "avg_hold_bars": 11.2,
              "backtest_range": [
                "2006-09-25",
                "2026-09-25"
              ],
              "measured": "Every detector-confirmed breakout \u2026 open trades are excluded.",
              "summary": "Falling wedge: 1,287 resolved trades from 2006-09-25 to 2026-09-25; 54% reached the target, average win +8.1%, average loss -4.2%, median win +6.3%."
            }
          }
        ]
      },
      "Universe": {
        "type": "object",
        "title": "Universe",
        "description": "The symbols the active nightly manifest covers.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "count": {
            "type": "integer",
            "description": "Number of symbols."
          },
          "symbols": {
            "type": "array",
            "description": "Every symbol the active nightly manifest covers, sorted. Empty when no manifest is active.",
            "items": {
              "type": "string",
              "description": "Exchange symbol, upper case."
            }
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "count": 2,
            "symbols": [
              "AAPL",
              "NVDA"
            ]
          }
        ]
      },
      "DemoIndex": {
        "type": "object",
        "title": "DemoIndex",
        "description": "The keyless demo: which symbols it answers for and where.",
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1",
            "description": "The contract version, always v1. Field names in v1 do not change, and v1 stays available at least 12 months after a v2 ships."
          },
          "as_of_session": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last trading session the data describes, as an ISO date. Setups change nightly, so say it when presenting data and do not cache setups across days. Null on routes that do not read a dated artifact (account, tickers, history, families, universe and the demo index).",
            "format": "date"
          },
          "data_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the stored artifact the answer came from: the candidate-book generation for patterns, the sealed manifest for family records. Null when the route reads no versioned artifact."
          },
          "source": {
            "type": "object",
            "description": "Who produced the data. Constant on every response.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Always \"TradingPal\"."
              },
              "url": {
                "type": "string",
                "description": "Always https://tradingpal.io.",
                "format": "uri"
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "const": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes.",
            "description": "The educational-use disclaimer, constant on every response. Repeat it when presenting a setup as something the user might act on."
          },
          "demo": {
            "type": "boolean",
            "description": "Always true."
          },
          "symbols": {
            "type": "array",
            "description": "The demo symbols, the only ones the demo routes answer for.",
            "items": {
              "type": "string",
              "description": "Exchange symbol, upper case."
            }
          },
          "patterns_url": {
            "type": "string",
            "description": "URL template of the demo patterns route, with {symbol} to fill in."
          },
          "chart_url": {
            "type": "string",
            "description": "URL template of the demo chart route, with {symbol} and the setup_id query value to fill in."
          },
          "next": {
            "type": "string",
            "description": "A sentence on where to create a key to track other tickers."
          }
        },
        "required": [
          "api_version",
          "as_of_session",
          "source",
          "disclaimer"
        ],
        "examples": [
          {
            "api_version": "v1",
            "demo": true,
            "symbols": [
              "AAPL",
              "NVDA"
            ]
          }
        ]
      },
      "Index": {
        "type": "object",
        "title": "Index",
        "description": "Where everything is, for an agent that lands on the base URL.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The API's name."
          },
          "api_version": {
            "type": "string",
            "description": "The contract version, v1."
          },
          "description": {
            "type": "string",
            "description": "One sentence on what the API serves."
          },
          "docs_url": {
            "type": "string",
            "description": "The developer docs, where keys are created.",
            "format": "uri"
          },
          "llms_txt": {
            "type": "string",
            "description": "URL of the agent reference (llms.txt).",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "description": "URL of this OpenAPI document.",
            "format": "uri"
          },
          "skill": {
            "type": "string",
            "description": "URL of the installable SKILL.md.",
            "format": "uri"
          },
          "auth": {
            "type": "object",
            "description": "How to authenticate.",
            "properties": {
              "header": {
                "type": "string",
                "description": "The Authorization header to send with a key."
              },
              "keys_url": {
                "type": "string",
                "description": "Where to create a key.",
                "format": "uri"
              },
              "client_header": {
                "type": "string",
                "description": "The optional X-TradingPal-Client header, which names the calling agent or script in the usage log."
              }
            }
          },
          "mcp": {
            "type": "object",
            "description": "The MCP server and how to connect to it.",
            "properties": {
              "url": {
                "type": "string",
                "description": "The MCP endpoint.",
                "format": "uri"
              },
              "transport": {
                "type": "string",
                "description": "The MCP transport; streamable-http."
              },
              "auth": {
                "type": "string",
                "description": "How the MCP server authenticates: the same Authorization header, optional for the demo tools."
              },
              "install": {
                "type": "object",
                "description": "Copy-and-paste install commands per client.",
                "properties": {
                  "claude_code": {
                    "type": "string",
                    "description": "The claude mcp add command."
                  },
                  "codex": {
                    "type": "string",
                    "description": "The codex mcp add command."
                  }
                }
              }
            }
          },
          "demo": {
            "type": "object",
            "description": "The keyless demo.",
            "properties": {
              "symbols": {
                "type": "array",
                "description": "The demo symbols.",
                "items": {
                  "type": "string",
                  "description": "Exchange symbol, upper case."
                }
              },
              "patterns_url": {
                "type": "string",
                "description": "The demo patterns route for the first demo symbol.",
                "format": "uri"
              },
              "families_url": {
                "type": "string",
                "description": "The keyless families route.",
                "format": "uri"
              }
            }
          },
          "limits": {
            "type": "object",
            "description": "Rate limits.",
            "properties": {
              "requests_per_minute": {
                "type": "integer",
                "description": "Per-key burst limit, requests per minute (60)."
              },
              "requests_per_day": {
                "type": "integer",
                "description": "Per-key daily limit, requests per day (10,000)."
              },
              "keyless_per_minute_per_ip": {
                "type": "integer",
                "description": "Limit for calls without a key, requests per minute per IP address (30)."
              }
            }
          },
          "disclaimer": {
            "type": "string",
            "description": "The educational-use disclaimer."
          }
        },
        "examples": [
          {
            "name": "TradingPal pattern API",
            "api_version": "v1",
            "description": "Breakout patterns TradingPal found on stocks & crypto in the last nightly scan, with the historical win rate, the lines to draw and a suggested entry, stop and target for each one.",
            "docs_url": "https://tradingpal.io/developers",
            "llms_txt": "https://api.tradingpal.io/api/v1/llms.txt",
            "openapi": "https://api.tradingpal.io/api/v1/openapi.json",
            "skill": "https://api.tradingpal.io/api/v1/skill.md",
            "auth": {
              "header": "Authorization: Bearer tp_live_\u2026",
              "keys_url": "https://tradingpal.io/developers",
              "client_header": "X-TradingPal-Client: <agent or script name> (optional)"
            },
            "mcp": {
              "url": "https://api.tradingpal.io/mcp",
              "transport": "streamable-http",
              "auth": "same Authorization header; connects without one for the demo tools",
              "install": {
                "claude_code": "claude mcp add --transport http tradingpal https://api.tradingpal.io/mcp --header \"Authorization: Bearer tp_live_\u2026\"",
                "codex": "codex mcp add tradingpal --url https://api.tradingpal.io/mcp"
              }
            },
            "demo": {
              "symbols": [
                "AAPL",
                "NVDA"
              ],
              "patterns_url": "https://api.tradingpal.io/api/v1/demo/AAPL",
              "families_url": "https://api.tradingpal.io/api/v1/families"
            },
            "limits": {
              "requests_per_minute": 60,
              "requests_per_day": 10000,
              "keyless_per_minute_per_ip": 30
            },
            "disclaimer": "Educational breakout-pattern analysis from TradingPal, not investment advice. Past results do not guarantee future outcomes."
          }
        ]
      },
      "Key": {
        "type": "object",
        "title": "Key",
        "description": "One API key as the developers page lists it. The full secret appears only in the POST /keys response.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Numeric id of the key; the key_id of DELETE /keys/{key_id}."
          },
          "prefix": {
            "type": "string",
            "description": "The first 16 characters of the key (tp_live_ plus eight), enough to tell keys apart. The rest is never shown again."
          },
          "name": {
            "type": "string",
            "description": "The label given at creation (default \"default\"), at most 64 characters."
          },
          "created_at": {
            "type": "string",
            "description": "When the key was created, ISO 8601 date-time.",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the key last authenticated a request, refreshed at most every five minutes. Null until first use.",
            "format": "date-time"
          },
          "key": {
            "type": "string",
            "description": "The full secret key, present only in the POST /keys response. Store it: only its hash is kept, so it cannot be shown again."
          }
        },
        "examples": [
          {
            "id": 12,
            "prefix": "tp_live_a1b2c3d4",
            "name": "my-agent",
            "created_at": "2026-09-01T14:03:11+00:00",
            "last_used_at": "2026-09-25T09:12:40+00:00",
            "key": "tp_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
          }
        ]
      },
      "Keys": {
        "type": "object",
        "title": "Keys",
        "description": "The signed-in site user's active keys.",
        "properties": {
          "keys": {
            "type": "array",
            "description": "The user's active (unrevoked) keys, newest first, without secrets.",
            "items": {
              "$ref": "#/components/schemas/Key",
              "description": "One key, without the secret."
            }
          }
        },
        "examples": [
          {
            "keys": [
              {
                "id": 12,
                "prefix": "tp_live_a1b2c3d4",
                "name": "my-agent",
                "created_at": "2026-09-01T14:03:11+00:00",
                "last_used_at": "2026-09-25T09:12:40+00:00"
              }
            ]
          }
        ]
      },
      "KeyRevoked": {
        "type": "object",
        "title": "KeyRevoked",
        "description": "Confirmation that a key was revoked.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The id of the revoked key."
          },
          "revoked_at": {
            "type": "string",
            "description": "When it was revoked, ISO 8601 date-time.",
            "format": "date-time"
          }
        },
        "examples": [
          {
            "id": 12,
            "revoked_at": "2026-09-25T10:00:00+00:00"
          }
        ]
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    },
    {
      "OAuth2": [
        "patterns"
      ]
    }
  ],
  "externalDocs": {
    "url": "https://api.tradingpal.io/api/v1/llms.txt",
    "description": "The reference for agents"
  }
}