{
  "version": "1",
  "schema": "https://tapeboard.com/schemas/mcp-actions/v1.json",
  "generated_at": "2026-05-02T00:00:00Z",
  "source_of_truth": "project/wk0/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) 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 (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-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"
  }
}
