{
 "version": "1",
 "schema": "https://tapeboard.com/schemas/mcp-actions/v1.json",
 "generated_at": "2026-08-26T08:30:00Z",
 "source_of_truth": "project/notes/action-id-registry.md",
 "service": {
  "name": "Tapeboard",
  "homepage": "https://tapeboard.com",
  "api_base_url": "https://tapeboard.com",
  "contact": "support@tapeboard.com"
 },
 "actions": [
  {
   "id": "search-ticker",
   "kind": "read",
   "description": "Search for a stock ticker by symbol or company name. Returns matching tickers with their exchange.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/v1/tickers/search",
   "idempotent": true,
   "params": [
    {
     "name": "query",
     "in": "query",
     "type": "string",
     "required": true,
     "validation": "1-32 chars, trim, reject control chars"
    },
    {
     "name": "limit",
     "in": "query",
     "type": "integer",
     "required": false,
     "default": 10,
     "validation": "1-25"
    }
   ],
   "returns": {
    "results": [
     {
      "symbol": "string",
      "name": "string",
      "exchange": "string"
     }
    ],
    "query_echo": "string"
   },
   "error_codes": [
    "invalid_params",
    "rate_limited"
   ],
   "rate_limit": {
    "per_ip": "60/min",
    "per_ip_secondary": "600/hr"
   }
  },
  {
   "id": "run-scanner",
   "kind": "read",
   "description": "Run a named market scanner (e.g. top-gainers, unusual-volume, penny-movers) and return the top-N matching tickers. Delayed-redistributable Schwab feed.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/scanner/{slug}",
   "idempotent": true,
   "params": [
    {
     "name": "slug",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "one of: top-gainers, top-losers, most-active, unusual-volume, gap-up, gap-down, most-shorted, penny-movers (404 otherwise)"
    },
    {
     "name": "limit",
     "in": "query",
     "type": "integer",
     "required": false,
     "default": 20,
     "validation": "1-50"
    }
   ],
   "returns": {
    "slug": "string",
    "as_of": "iso8601",
    "delayed_minutes": "integer",
    "source": "string",
    "license": "string",
    "disclaimer": "string",
    "cache_status": "string",
    "rows": [
     {
      "symbol": "string",
      "price": "number",
      "change_pct": "number",
      "volume": "integer",
      "rvol": "number|null",
      "market_cap": "integer|null",
      "sector": "string|null"
     }
    ]
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "60/min",
    "per_ip_resource": "30/min per (IP,slug)"
   },
   "headers_returned": [
    "X-Cache-Status"
   ],
   "ship_notes": "Wk1 day2 ship B-1: endpoint moved from /api/v1/scanners/{slug} (registry draft) to /api/scanner/{slug} to match the live Worker route prefix; rows shape extended with rvol + sector for client-side ranking parity with the authed scanner."
  },
  {
   "id": "get-quote",
   "kind": "read",
   "description": "Get the current quote for a ticker, including price, change, volume, and market cap. Delayed at least 15 minutes; redistributable under Schwab license.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/quote/{ticker}",
   "idempotent": true,
   "params": [
    {
     "name": "ticker",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "1-5 letters with optional .X or -X class suffix (BRK.B, RDS-A)"
    }
   ],
   "returns": {
    "symbol": "string",
    "price": "number",
    "change": "number",
    "change_pct": "number",
    "volume": "integer",
    "market_cap": "integer",
    "last_updated": "iso8601",
    "delayed_minutes": "integer",
    "source": "string",
    "license": "string",
    "disclaimer": "string",
    "cache_status": "string"
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "60/min",
    "per_ip_resource": "10/min per (IP,ticker)"
   },
   "headers_returned": [
    "X-Cache-Status"
   ],
   "ship_notes": "Wk1 day2 ship B-1: endpoint moved from /api/v1/tickers/{ticker}/quote (registry draft) to /api/quote/{ticker} to match the live Worker route prefix; response augmented with source/license/disclaimer/cache_status per pre-flight \u00a7B-1 \u00a76."
  },
  {
   "id": "get-squeeze",
   "kind": "read",
   "description": "Get the short-squeeze score and component breakdown for a ticker (short interest, days-to-cover, float, borrow rate).",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/v1/tickers/{ticker}/squeeze",
   "idempotent": true,
   "params": [
    {
     "name": "ticker",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "1-6 chars, uppercase"
    }
   ],
   "returns": {
    "symbol": "string",
    "score": "integer",
    "score_band": "string",
    "components": {
     "short_interest_pct_float": "number",
     "days_to_cover": "number",
     "float_shares": "integer",
     "borrow_rate_pct": "number",
     "utilization_pct": "number"
    },
    "as_of": "iso8601",
    "data_age_hours": "integer"
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "60/min",
    "per_ip_resource": "15/min per (IP,ticker)"
   }
  },
  {
   "id": "get-news",
   "kind": "read",
   "description": "Get recent news headlines for a ticker, ordered most-recent first.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/v1/tickers/{ticker}/news",
   "idempotent": true,
   "params": [
    {
     "name": "ticker",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "1-6 chars, uppercase"
    },
    {
     "name": "limit",
     "in": "query",
     "type": "integer",
     "required": false,
     "default": 10,
     "validation": "1-25"
    }
   ],
   "returns": {
    "symbol": "string",
    "items": [
     {
      "id": "string",
      "headline": "string",
      "source": "string",
      "url": "string",
      "published_at": "iso8601"
     }
    ]
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "60/min",
    "per_ip_resource": "15/min per (IP,ticker)"
   }
  },
  {
   "id": "get-stablecoin-pulse",
   "kind": "read",
   "description": "Net stablecoin supply across all issuers \u2014 total + 24h/7d deltas + per-issuer breakdown (top 20). Leading-indicator signal: when stablecoin supply expands, capital is flowing INTO crypto. Data source: DeFiLlama.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/crypto/stablecoin-pulse",
   "idempotent": true,
   "params": [],
   "returns": {
    "total_supply_usd": "number \u2014 sum across all issuers",
    "total_change_24h_usd": "number \u2014 net 24h flow",
    "total_change_24h_pct": "number",
    "total_change_7d_usd": "number",
    "total_change_7d_pct": "number",
    "issuers": "array \u2014 top 20 by circulating; each has symbol, circulating_usd, 24h+7d deltas, peg_type, peg_mechanism",
    "issuer_count": "integer \u2014 total in source",
    "source": "string \u2014 'defillama'"
   },
   "error_codes": [
    "rate_limited"
   ],
   "rate_limit": {
    "per_ip": "60/min"
   },
   "cache_ttl_seconds": 1800
  },
  {
   "id": "get-funding-rates",
   "kind": "read",
   "description": "BTC perpetual funding rates across 4 major exchanges (Binance, Bybit, OKX, Hyperliquid) + cross-venue average + sentiment bucket. Leading-indicator signal: positive funding = longs paying = sentiment overheated; negative = capitulation. Settlement cadence is 8h for Binance/Bybit/OKX, 1h for Hyperliquid.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/crypto/funding-rates",
   "idempotent": true,
   "params": [],
   "returns": {
    "symbol": "string \u2014 'BTC'",
    "rates": "array \u2014 per-venue: exchange, symbol, rate_per_period, rate_annualized_pct, next_settlement_ms, optional error",
    "avg_annualized_pct": "number \u2014 cross-venue average",
    "sentiment_bucket": "enum \u2014 'capitulation' | 'bearish' | 'neutral' | 'bullish' | 'froth'",
    "generated_at_ms": "integer"
   },
   "error_codes": [
    "rate_limited"
   ],
   "rate_limit": {
    "per_ip": "60/min"
   },
   "cache_ttl_seconds": 300
  },
  {
   "id": "get-mstr-premium",
   "kind": "read",
   "description": "MicroStrategy (MSTR) market cap divided by the USD value of its BTC stack \u2014 the 'mNAV' premium. Leading-indicator signal: mNAV > 2.5 means retail is leveraging BTC exposure via equity (warning); mNAV < 1.5 means MSTR compressed to near-par with its BTC stack (setup). Data source: CoinGecko company-holdings + live Schwab fundamentals for MSTR market cap.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/crypto/mstr-premium",
   "idempotent": true,
   "params": [],
   "returns": {
    "mstr_price_usd": "number \u2014 live MSTR quote (rounded)",
    "mstr_market_cap_usd": "integer \u2014 Schwab marketCap field when available, else price \u00d7 shares",
    "mstr_shares_outstanding": "integer \u2014 Schwab raw share count",
    "mstr_market_cap_source": "enum \u2014 'schwab_direct' | 'computed'",
    "btc_holdings": "integer \u2014 MSTR's BTC stack (~843,738 as of 2026-05)",
    "btc_holdings_value_usd": "integer",
    "btc_price_implied": "number \u2014 CoinGecko-implied price = value / holdings",
    "mnav": "number \u2014 market_cap / btc_holdings_value (3dp)",
    "sentiment_bucket": "enum \u2014 'discount' (<1.0) | 'compressed' (1.0-1.5) | 'normal' (1.5-2.5) | 'elevated' (2.5-3.5) | 'frothy' (>3.5)",
    "quote_source": "enum \u2014 'schwab' | 'finnhub' | 'yahoo' | 'stooq'",
    "generated_at_ms": "integer"
   },
   "error_codes": [
    "rate_limited",
    "service_unavailable"
   ],
   "rate_limit": {
    "per_ip": "60/min"
   },
   "cache_ttl_seconds": 600
  },
  {
   "id": "get-etf-pulse",
   "kind": "read",
   "description": "Spot Bitcoin ETF pulse \u2014 per-ETF AUM, 24h volume, 24h price change, and share-of-pool across the 10 US spot BTC ETFs (IBIT, FBTC, ARKB, BITB, BTCO, HODL, BRRR, BTC, EZBC, GBTC). Closes the Crypto Macro Dashboard 4-up. Data source: Schwab batch quote with fundamentals (marketCap = AUM proxy). v1 ships pulse only; net-flow estimation deferred to v2.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/crypto/etf-pulse",
   "idempotent": true,
   "params": [],
   "returns": {
    "total_aum_usd": "integer \u2014 sum of marketCap across the 10 ETFs",
    "total_volume_24h_usd": "integer \u2014 sum of volume_24h_usd",
    "etf_count": "integer \u2014 ETFs with a live price",
    "etfs": "array of {symbol, name, price_usd, change_24h_pct, aum_usd, aum_share_pct, volume_24h_shares, volume_24h_usd} sorted by AUM desc",
    "source": "literal 'schwab'",
    "generated_at_ms": "integer"
   },
   "error_codes": [
    "rate_limited",
    "service_unavailable"
   ],
   "rate_limit": {
    "per_ip": "60/min"
   },
   "cache_ttl_seconds": 300
  },
  {
   "id": "get-filing-summary",
   "kind": "read",
   "description": "Get an AI-generated summary of an SEC filing (8-K, 10-K, 10-Q) for a ticker.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/v1/tickers/{ticker}/filings/{filing_type}/summary",
   "idempotent": true,
   "params": [
    {
     "name": "ticker",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "1-6 chars, uppercase"
    },
    {
     "name": "filing_type",
     "in": "path",
     "type": "enum",
     "required": true,
     "enum": [
      "8-K",
      "10-K",
      "10-Q"
     ]
    },
    {
     "name": "filing_id",
     "in": "query",
     "type": "string",
     "required": false,
     "validation": "if omitted, returns most recent of that type"
    }
   ],
   "returns": {
    "filing_id": "string",
    "symbol": "string",
    "filing_type": "string",
    "filed_at": "iso8601",
    "summary": "string",
    "key_points": [
     "string"
    ],
    "source_url": "string",
    "summary_model_version": "string"
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "30/min",
    "per_ip_resource": "10/min per (IP,ticker)"
   }
  },
  {
   "id": "add-to-watchlist",
   "kind": "write",
   "description": "Add a ticker to a watchlist. If watchlist_id is null and the user has no session, a guest watchlist is created and a guest_token is returned in the receipt.",
   "auth": "guest-token-or-session",
   "method": "POST",
   "endpoint": "/api/v1/watchlists/items",
   "idempotent": true,
   "idempotency_key_header": "Idempotency-Key",
   "params": [
    {
     "name": "ticker",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "1-6 chars, uppercase"
    },
    {
     "name": "watchlist_id",
     "in": "body",
     "type": "string",
     "required": false,
     "nullable": true,
     "validation": "if null, default/guest list used"
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "undo_url": "string",
    "list_state": {
     "watchlist_id": "string",
     "name": "string",
     "ticker_count": "integer",
     "is_guest": "boolean"
    }
   },
   "error_codes": [
    "auth_required",
    "not_found",
    "rate_limited",
    "invalid_params",
    "duplicate"
   ],
   "rate_limit": {
    "per_ip": "30/min",
    "per_ip_resource": "10/min per (IP,ticker)"
   }
  },
  {
   "id": "save-thesis",
   "kind": "write",
   "description": "Save a personal note (thesis) on a filing. Notes are private to the user/guest.",
   "auth": "guest-token-or-session",
   "method": "POST",
   "endpoint": "/api/v1/filings/{filing_id}/theses",
   "idempotent": true,
   "idempotency_key_header": "Idempotency-Key",
   "params": [
    {
     "name": "filing_id",
     "in": "path",
     "type": "string",
     "required": true
    },
    {
     "name": "note",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "1-4000 chars"
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "undo_url": "string",
    "thesis_id": "string"
   },
   "error_codes": [
    "auth_required",
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "20/min",
    "per_ip_resource": "5/min per (IP,filing_id)"
   }
  },
  {
   "id": "share-filing",
   "kind": "write",
   "description": "Generate a public shareable link and social card for a filing summary. Returns share_url (canonical) and social_card_url (OG image).",
   "auth": "none",
   "method": "POST",
   "endpoint": "/api/v1/filings/{filing_id}/share",
   "idempotent": true,
   "idempotency_key_header": "Idempotency-Key",
   "params": [
    {
     "name": "filing_id",
     "in": "path",
     "type": "string",
     "required": true
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "share_url": "string",
    "social_card_url": "string",
    "undo_url": "string"
   },
   "error_codes": [
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "30/min",
    "per_ip_resource": "10/min per (IP,filing_id)"
   }
  },
  {
   "id": "set-price-alert",
   "kind": "write",
   "description": "Set a price alert that fires when a ticker crosses a threshold. Direction is 'above' or 'below'.",
   "auth": "guest-token-or-session",
   "method": "POST",
   "endpoint": "/api/v1/alerts/price",
   "idempotent": true,
   "idempotency_key_header": "Idempotency-Key",
   "params": [
    {
     "name": "ticker",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "1-6 chars, uppercase"
    },
    {
     "name": "threshold",
     "in": "body",
     "type": "number",
     "required": true,
     "validation": "> 0, max 6 decimals"
    },
    {
     "name": "direction",
     "in": "body",
     "type": "enum",
     "required": true,
     "enum": [
      "above",
      "below"
     ]
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "undo_url": "string",
    "alert_id": "string"
   },
   "error_codes": [
    "auth_required",
    "not_found",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "20/min",
    "per_ip_resource": "5/min per (IP,ticker)"
   }
  },
  {
   "id": "signup-account",
   "kind": "write",
   "description": "Create a new Tapeboard account with email + password. Returns a session cookie on success. Anti-enumeration: duplicates return 201 with no Set-Cookie.",
   "auth": "none",
   "method": "POST",
   "endpoint": "/api/accounts/register",
   "idempotent": false,
   "params": [
    {
     "name": "email",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "RFC 5321 email, \u2264254 chars, lowercase normalized"
    },
    {
     "name": "password",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "8-128 chars, NIST 800-63B (no max-complexity rules)"
    },
    {
     "name": "age_confirmed",
     "in": "body",
     "type": "boolean",
     "required": true,
     "validation": "must be true; UI checkbox required"
    },
    {
     "name": "terms_accepted",
     "in": "body",
     "type": "boolean",
     "required": true,
     "validation": "must be true; UI checkbox required"
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "undo_url": "string",
    "user_id": "string"
   },
   "error_codes": [
    "invalid_params",
    "rate_limited",
    "terms_required",
    "weak_password"
   ],
   "rate_limit": {
    "per_ip": "10/hr",
    "per_ip_resource": "3/hr per (IP,email-hash)"
   }
  },
  {
   "id": "claim-guest-watchlist",
   "kind": "write",
   "description": "Claim a guest watchlist into an authenticated user account. Merges guest items into the user's default list. Called once after signup completes.",
   "auth": "session",
   "method": "POST",
   "endpoint": "/api/v1/watchlists/claim",
   "idempotent": true,
   "idempotency_key_header": "Idempotency-Key",
   "idempotency_key_basis": "guest_token",
   "params": [
    {
     "name": "guest_token",
     "in": "body",
     "type": "string",
     "required": true,
     "validation": "server-issued opaque token, 30d TTL"
    }
   ],
   "returns": {
    "receipt_id": "string",
    "human_summary": "string",
    "undo_url": "string",
    "watchlist_id": "string",
    "claim_status": "merged|already_claimed|expired|invalid",
    "items_merged": "integer",
    "items_duplicate": "integer"
   },
   "error_codes": [
    "auth_required",
    "not_found",
    "expired",
    "rate_limited",
    "invalid_params"
   ],
   "rate_limit": {
    "per_ip": "5/min",
    "per_ip_resource": "3/min per (IP,guest_token)"
   }
  },
  {
   "id": "get-ai-breakdown",
   "kind": "read",
   "description": "Precomputed grounded 'BREAK IT DOWN' explainer for a stock: what's moving it right now, in plain English (catalyst, flow, context). Generated for top movers and squeeze names; returns content:null for symbols without a cached breakdown yet. Tool-not-advice; disclaimer embedded in content.",
   "auth": "none",
   "method": "GET",
   "endpoint": "/api/public/ai/breakdown/{ticker}",
   "idempotent": true,
   "params": [
    {
     "name": "ticker",
     "in": "path",
     "type": "string",
     "required": true,
     "validation": "US equity symbol, A-Z, 1-6 chars"
    }
   ],
   "returns": {
    "ok": "boolean",
    "data": {
     "symbol": "string",
     "content": "string | null \u2014 markdown explainer; null = not precomputed yet (top movers get priority)",
     "generatedAt": "string | null"
    }
   },
   "error_codes": [
    "invalid_symbol",
    "rate_limited"
   ],
   "notes": "2026-08-26: added after observing an AI agent (Lightpanda) rendering /research/<SYM> HTML on a loop to read breakdowns \u2014 this JSON endpoint is the intended, cheap, stable surface for exactly that. Prefer it over page rendering."
  }
 ],
 "receipts": {
  "shape_url": "https://tapeboard.com/schemas/receipt/v1.json",
  "endpoints": {
   "read": {
    "method": "GET",
    "path": "/api/v1/receipts/{receipt_id}",
    "auth": "actor-or-admin"
   },
   "undo": {
    "method": "POST",
    "path": "/api/v1/receipts/{receipt_id}/undo",
    "auth": "undo-token-or-session"
   }
  },
  "undo_token_ttl_days": 30
 },
 "auth_modes": {
  "none": "public, no header required",
  "guest-token": "server-issued opaque token (cookie or X-Guest-Token header), survives 30d, claimable into a session",
  "session": "authenticated user, cookie-based",
  "guest-token-or-session": "either guest-token or session is acceptable"
 }
}