{
  "version": "1",
  "schema": "https://tapeboard.com/schemas/mcp-actions/v1.json",
  "generated_at": "2026-05-02T00:00: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 §B-1 §6."
    },
    {
      "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 — 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 — sum across all issuers",
        "total_change_24h_usd": "number — net 24h flow",
        "total_change_24h_pct": "number",
        "total_change_7d_usd": "number",
        "total_change_7d_pct": "number",
        "issuers": "array — top 20 by circulating; each has symbol, circulating_usd, 24h+7d deltas, peg_type, peg_mechanism",
        "issuer_count": "integer — total in source",
        "source": "string — '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 — 'BTC'",
        "rates": "array — per-venue: exchange, symbol, rate_per_period, rate_annualized_pct, next_settlement_ms, optional error",
        "avg_annualized_pct": "number — cross-venue average",
        "sentiment_bucket": "enum — '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 — 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 — live MSTR quote (rounded)",
        "mstr_market_cap_usd": "integer — Schwab marketCap field when available, else price × shares",
        "mstr_shares_outstanding": "integer — Schwab raw share count",
        "mstr_market_cap_source": "enum — 'schwab_direct' | 'computed'",
        "btc_holdings": "integer — MSTR's BTC stack (~843,738 as of 2026-05)",
        "btc_holdings_value_usd": "integer",
        "btc_price_implied": "number — CoinGecko-implied price = value / holdings",
        "mnav": "number — market_cap / btc_holdings_value (3dp)",
        "sentiment_bucket": "enum — 'discount' (<1.0) | 'compressed' (1.0-1.5) | 'normal' (1.5-2.5) | 'elevated' (2.5-3.5) | 'frothy' (>3.5)",
        "quote_source": "enum — '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 — 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 — sum of marketCap across the 10 ETFs",
        "total_volume_24h_usd": "integer — sum of volume_24h_usd",
        "etf_count": "integer — 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, ≤254 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)"
      }
    }
  ],
  "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"
  }
}
