AlphaAI REST API

REST API for relevance-scored, ticker-linked financial news. Fifteen read-only operations across news feeds, trending, macro, an economic calendar, SEC Form 4 insider data, symbols, and per-ticker sentiment and insider rollups. Cursor-paginated, bearer API key auth, OpenAPI 3.1 served anonymously at /api/schema/.

OpenAPI Specification

alphaai-rest-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: alphai REST API
  version: "1.24.0"
  description: |
    Public REST API for alphai's relevance-scored, ticker-linked financial news.
    All endpoints accept `Authorization: Bearer ak_live_…` (issued from your
    `/account/api-keys`). Authenticated traffic is metered per account with two
    layers — a per-minute burst cap and a per-day volume cap (Free 20/min +
    100/day, Basic 60/min + 10,000/day, Pro 150/min + 100,000/day); a request
    passes only if both are under budget. The Free tier is for evaluation /
    personal non-commercial use; Basic is licensed for internal commercial use;
    Pro adds redistribution of the enriched feed. Send all API traffic to
    `api.alphai.io`; a key is required on every request.

    Every keyed response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`
    and `X-RateLimit-Reset`; these report the per-day volume layer (Reset =
    epoch seconds of the next 00:00 UTC reset) so you can watch your daily budget
    without provoking a 429. A per-minute burst surfaces only as a 429 with a
    short `Retry-After`. A 429 body also names your tier, its caps and an
    `upgrade` block. The headers reflect origin processing, so a response
    served from a shared cache may omit them.

    News-archive depth is tiered: Free keys can page the feeds back 30 days,
    Basic 90 days, Pro 180 days. Paging past your horizon returns `403`
    with an `extra.reason` of `archive_horizon` (see the ArchiveGated response).
    Coverage note: our collectors expanded in June 2026, so archive months
    before 2026-06 hold fewer articles per day than the current feed.

    Insider data (SEC EDGAR Form 4) flows through the same news shape:
    `category=insider` rows in the feed, a dedicated `/api/news/insider/`
    route, and 30-day per-ticker rollups at
    `/api/symbols/{ticker}/insider-summary/`. For charts and analysis there
    is `/api/symbols/{ticker}/insider-trades/`: the complete per-ticker
    event history with 3m/12m/all-time rollups, weekly and monthly dollar
    buckets, and every event of the trailing 12 months in one response.

    Market-wide macro coverage (central-bank decisions, CPI/jobs prints,
    commodities, geopolitics) is in the feed too, under the `macro_economy`,
    `commodities` and `geopolitics` categories and a dedicated
    `/api/news/macro/` route. Macro rows are market-wide events, so most
    carry an empty `tickers` list — filter them by category, not by symbol.

    The forward half of the macro loop is `/api/calendar/`: the official
    schedule of upcoming US macro releases (FOMC decisions and minutes, CPI,
    PPI, jobs report, GDP estimates, PCE, retail sales, jobless claims,
    JOLTS), each with a stable occurrence `uid` that survives reschedules.
    Ask the calendar what's coming, then read `/api/news/macro/` for what a
    release meant once it's out.

    Query parameters are validated strictly. An unknown or misspelled
    parameter returns 400 naming the field, and the error body carries
    `extra.allowed_params` — every parameter that endpoint accepts — so you
    never have to guess. Common mix-ups also get a did-you-mean (`limit` and
    `per_page` point to `page_size`; `offset`, `page`, `skip` and the
    `after_id`/`before_id` family point to `cursor`; `ticker` points to
    `symbol`). Parameters that belong to the MCP server rather than to this
    API — free-text `q`/`query`/`search`, and `min_actionability` — say so
    instead of failing silently.

    See https://alphai.io/developers for the score and sentiment legend, and
    https://alphai.io/pricing for tier limits.
  contact:
    name: alphai support
    email: support@alphai.io
    url: https://alphai.io/contact
  license:
    name: Proprietary

servers:
  - url: https://api.alphai.io
    description: Production (API host — key required)

security:
  - apiKey: []

tags:
  - name: news
    description: Relevance-scored, ticker-linked news.
  - name: symbols
    description: Active equity, crypto, and foreign symbols (tickers).
  - name: calendar
    description: Scheduled US macro releases (FOMC, CPI, jobs, GDP, PCE…).

paths:
  /api/news/:
    get:
      tags: [news]
      summary: List news (cursor-paginated)
      description: |
        Feed of enriched articles, newest first. Default filter:
        `relevance_score >= 4` AND at least one active ticker. Pagination is
        cursor-based with a default page size of 10 (any size from 1 to 20 via
        `page_size=`; Pro keys go up to 50): omit `cursor` for the newest page,
        then pass the
        `next_cursor` from each response to fetch the next (older) page.
        `next_cursor: null` means the end of the feed. Cursors are opaque — do
        not construct or parse them; an invalid cursor returns 400. Archive
        depth is tiered: paging back past your plan's horizon (Free 30 days,
        Basic 90, Pro 180) returns 403 with an upgrade hint.

        **Delta polling** (`sort=ingested`): the same feed ordered by the
        moment rows became available, for "what is new since my last poll".
        Articles reach the feed later than their publish time (collection
        median ~30 min for general news, with a long tail), so a poller that
        filters by `time_published` misses most late arrivals; `sort=ingested`
        never misses a row. First call without `cursor` returns the newest
        `page_size` rows and a cursor at the feed head; each later call with
        the previous `next_cursor` returns only rows added since. In this mode
        `next_cursor` is always non-null — empty `results` means you are
        caught up, keep the cursor and poll again later (responses are cached
        for 60 s, so polling more often than once a minute buys nothing). New
        rows only: an update to an already-delivered article is not re-sent.
        `time_published` is not monotonic within a delta page; sort client-side
        if you need chronological order. A cursor is only valid with the sort
        mode that issued it.

        Delta mode carries live coverage only: when we add history in bulk
        (backfilling an earlier period), those rows do NOT enter this stream —
        they would arrive as thousands of "new" items that are months old and
        push your cursor far behind the live feed. They are served normally in
        `sort=published`, by date filters and by `/api/news/{uid}/`, so a
        one-off catch-up over the archive is a published-mode query.

        **Keeping up.** One call returns at most `page_size` rows, so a poller
        holds its position at the head only while `calls per day × page_size`
        stays above the daily volume of the stream it asked for. Below that it
        falls a little further behind every day, and the symptom misleads:
        `time_published` reads hours or days old while the data is current.
        What went stale is the cursor, not the feed. So drain instead of
        polling once per tick: when a page comes back with `results` filled,
        call again immediately, and sleep only once `results` is empty. That
        clears a burst within the same cycle and lets a poller catch up by
        itself after downtime. Two levers if the call budget is still short,
        and they multiply: `page_size` (up to 20, or 50 on a Pro key) and a
        narrower stream (`min_relevance`, `symbol`, `category`). For scale, at
        the default `>= 4` floor the feed carries roughly 2,700 rows a day,
        while `min_relevance=7` carries roughly 1,000.

        A poller that never catches up eventually meets the archive gate: the
        403 below is keyed on the age of the first row you have not read yet,
        so it fires on a delta poller that never paged back at all.
      parameters:
        - in: query
          name: cursor
          description: Opaque cursor from a prior response's `next_cursor`. Omit for the newest page.
          schema: { type: string }
        - in: query
          name: symbol
          description: >-
            Filter to articles that mention this ticker. US equities use the
            bare symbol (`NVDA`); cryptocurrencies use the `<SYM>-USD` form
            (`BTC-USD`), and a bare crypto name resolves to it (`DOGE` matches
            `DOGE-USD`); foreign listings use the Yahoo suffix (`VOD.L`). A
            delisted symbol returns its news history. A renamed company's
            current ticker also matches articles tagged with its former
            ticker, and any share class of an issuer matches articles tagged
            with its other listed classes (`GOOGL` includes rows tagged
            `GOOG`); article tags stay as published.
          schema: { type: string, example: "NVDA" }
        - in: query
          name: category
          description: >-
            Keep only these categories. Accepts a single value, a CSV list
            (`category=earnings,insider`), or a repeated parameter — matches
            any of them (OR).
          style: form
          explode: true
          schema:
            type: array
            items: { $ref: "#/components/schemas/NewsCategory" }
        - in: query
          name: exclude_categories
          description: >-
            Drop these categories from the feed. Same single / CSV / repeated
            forms as `category`.
          style: form
          explode: true
          schema:
            type: array
            items: { $ref: "#/components/schemas/NewsCategory" }
        - in: query
          name: min_relevance
          description: Override the default ≥4 threshold.
          schema: { type: integer, minimum: 1, maximum: 10, default: 4 }
        - in: query
          name: page_size
          description: >-
            Items per page, default `10`. Any value from `1` to `20` is accepted
            on every tier; `21`-`50` requires a Pro key. A value outside `1`-`50`,
            or an over-`20` page without a Pro key, returns 400 — the page is
            never silently clamped. Pagination is otherwise unchanged: pass each
            response's `next_cursor` back as `cursor` regardless of page size.
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
        - in: query
          name: collapse
          description: >-
            `story` collapses coverage of one event to a single representative
            row (the story root): syndicated reprints and independently written
            articles about the same event, matched on headline and lead
            similarity. Populates the `story_id`, `sources_count` and `sources`
            fields on each item. Omit for the full feed (every article, story
            fields `null`). Only `story` is accepted; any other value returns
            400.

            Note: row-level filters (`symbol`, `category`, `min_relevance`)
            apply to the story's representative root, so a story whose root does
            not match the filter is omitted entirely.
          schema: { type: string, enum: [story] }
        - in: query
          name: sort
          description: >-
            `published` (default) is the reverse-chronological feed.
            `ingested` is delta-polling mode: rows in the order they became
            available, ascending, with `next_cursor` always returned (empty
            `results` = caught up) — see the endpoint description. All other
            filters apply unchanged in both modes. Cursors are mode-specific:
            a cursor is only valid with the sort mode that issued it (a
            mismatch returns 400).
          schema: { type: string, enum: [published, ingested], default: published }
      responses:
        "200":
          description: A page of enriched articles.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NewsPagination" }
        "400": { description: Invalid cursor (opaque; reuse `next_cursor`), a cursor replayed into the other `sort` mode, unsupported `collapse`/`sort` value, or a `page_size` outside 1-50 or not permitted on this tier. }
        "403": { $ref: "#/components/responses/ArchiveGated" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/news/trending/:
    get:
      tags: [news]
      summary: Trending news (last 48h)
      description: >-
        Top stories from the trailing 48h: `relevance_score >= 8`, ranked by
        relevance score decayed by article age, so a fresh story outranks an
        equally-scored stale one. At most two stories per ticker. Fixed size
        of 10, no pagination. Coverage of one story, syndicated reprints and
        independent write-ups of the same event alike, is collapsed to a
        single representative, and each item carries the `story_id`,
        `sources_count` and `sources` fields for the story it represents.
        Most stories are carried by a single outlet, so `sources_count` is
        usually 1; see its field description.
      responses:
        "200":
          description: List of enriched articles.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/RichNewsArticle" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/news/insider/:
    get:
      tags: [news]
      summary: Insider-transaction news (SEC Form 4)
      description: |
        Dedicated insider feed (`category=insider`): SEC EDGAR Form 4 insider
        activity ONLY — one deterministic-templated row per filing event
        (a filing's non-derivative trades of one type and holding form,
        aggregated: total shares, volume-weighted average price and executed
        range, total value), surfaced shortly after filing, with the relevance
        score computed from the event's total (size, buy vs. sell, 10b5-1 plan
        or not). Same enriched shape, ticker
        and `relevance_score >= 4` filtering, and cursor-based pagination as
        `/api/news/`. Equivalent to `/api/news/?category=insider`, surfaced as a
        first-class route. For aggregate stats instead of the stream, see
        `/api/symbols/{ticker}/insider-summary/`.

        To watch for new filings, poll with `sort=ingested` rather than
        re-reading the newest page: a Form 4 is filed up to several days after
        the trade it reports, so a new event often enters the feed already
        below the head of the publish-ordered page. Delta mode orders by the
        moment the row became available, so it never misses one. The contract
        is identical to `/api/news/?sort=ingested` — see that endpoint for the
        full description.
      parameters:
        - in: query
          name: cursor
          description: Opaque cursor from a prior response's `next_cursor`. Omit for the newest page.
          schema: { type: string }
        - in: query
          name: symbol
          description: >-
            Filter to insider news that mentions this ticker. Delisted
            symbols return their history; a renamed company's current ticker
            also matches rows tagged with its former ticker.
          schema: { type: string, example: "NVDA" }
        - in: query
          name: min_relevance
          description: >-
            Override the default ≥4 threshold. Insider rows score
            deterministically from the event's summed dollar value (plus
            buy/10b5-1 modifiers), so this acts as an "only large trades"
            dial — e.g. `min_relevance=7` keeps roughly $10M+ events.
          schema: { type: integer, minimum: 1, maximum: 10, default: 4 }
        - in: query
          name: page_size
          description: >-
            Items per page, default `10`. Any value from `1` to `20` is accepted
            on every tier; `21`-`50` requires a Pro key. A value outside `1`-`50`,
            or an over-`20` page without a Pro key, returns 400.
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
        - in: query
          name: sort
          description: >-
            `published` (default) is the reverse-chronological feed.
            `ingested` is delta-polling mode, with the same contract as on
            `/api/news/`: rows in the order they became available, ascending,
            `next_cursor` always returned (empty `results` = caught up), and
            all other filters applied unchanged. Prefer it for watching insider
            activity — a Form 4 is filed days after the trade it reports, so a
            new event routinely lands below the newest page of the
            publish-ordered feed and a poller reading only the head misses it.
            Cursors are mode-specific: a cursor is only valid with the sort mode
            that issued it (a mismatch returns 400).
          schema: { type: string, enum: [published, ingested], default: published }
      responses:
        "200":
          description: >-
            A page of enriched insider-transaction articles. Each item
            additionally carries the structured `insider` event block
            (side / shares / average price / total value / reporting owner) —
            populated on this endpoint only.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NewsPagination" }
        "400": { description: Invalid cursor (opaque; reuse `next_cursor`), a cursor replayed into the other `sort` mode, an unsupported `sort` value, or a `page_size` outside 1-50 or not permitted on this tier. }
        "403": { $ref: "#/components/responses/ArchiveGated" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/news/macro/:
    get:
      tags: [news]
      summary: Market-wide macro news (central banks, prints, commodities, geopolitics)
      description: |
        Dedicated macro feed: the `macro_economy`, `commodities` and
        `geopolitics` categories in one stream — central-bank decisions and
        statements (FOMC), inflation/jobs prints, oil and gold, geopolitical
        risk. Same enriched shape, `relevance_score >= 4` floor and
        cursor-based pagination as `/api/news/`; equivalent to
        `/api/news/?category=macro_economy,commodities,geopolitics`, surfaced
        as a first-class route.

        Macro rows are market-wide, so most carry an empty `tickers` list —
        that is by design (an FOMC statement is not news about one company),
        and the per-article analysis block is still populated. A macro row
        that IS about listed companies (an oil-price story tagged to majors)
        keeps its tickers.

        To watch for new releases, poll with `sort=ingested` — same delta
        contract as `/api/news/?sort=ingested`.
      parameters:
        - in: query
          name: cursor
          description: Opaque cursor from a prior response's `next_cursor`. Omit for the newest page.
          schema: { type: string }
        - in: query
          name: category
          description: >-
            Narrow to a subset of the macro categories, comma-separated.
            Values outside `macro_economy`/`commodities`/`geopolitics` return
            400 (the full category filter lives on `/api/news/`).
          schema: { type: string, example: "macro_economy" }
        - in: query
          name: min_relevance
          description: >-
            Override the default ≥4 threshold. On release days the release
            itself scores 7-9, so `min_relevance=7` keeps roughly
            "the events" and drops the commentary.
          schema: { type: integer, minimum: 1, maximum: 10, default: 4 }
        - in: query
          name: page_size
          description: >-
            Items per page, default `10`. Any value from `1` to `20` is accepted
            on every tier; `21`-`50` requires a Pro key. A value outside `1`-`50`,
            or an over-`20` page without a Pro key, returns 400.
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
        - in: query
          name: sort
          description: >-
            `published` (default) is the reverse-chronological feed.
            `ingested` is delta-polling mode, with the same contract as on
            `/api/news/`. Cursors are mode-specific: a cursor is only valid
            with the sort mode that issued it (a mismatch returns 400).
          schema: { type: string, enum: [published, ingested], default: published }
      responses:
        "200":
          description: A page of enriched macro articles.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NewsPagination" }
        "400": { description: Invalid cursor (opaque; reuse `next_cursor`), a cursor replayed into the other `sort` mode, a category outside the macro set, or a `page_size` outside 1-50 or not permitted on this tier. }
        "403": { $ref: "#/components/responses/ArchiveGated" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/calendar/:
    get:
      tags: [calendar]
      summary: Scheduled US macro releases (economic calendar)
      description: |
        The official forward schedule of US macro releases: FOMC decisions
        (with SEP and press-conference markers) and minutes, CPI, PPI, the
        jobs report (nonfarm payrolls), GDP estimates (advance/second/third),
        PCE, advance retail sales, weekly jobless claims and JOLTS — sourced
        from the agencies' own schedule pages (Fed, BLS, BEA, Census, DOL).

        Each occurrence carries a stable `uid` (`US-CPI-2026-07`) that
        survives reschedules: a moved release keeps its identity, updates
        `scheduled_at` and reports `schedule_status`. `phase` says only
        whether the scheduled moment has passed (`upcoming`/`elapsed`) — it
        deliberately does not claim the agency actually published. Pair the
        calendar with `/api/news/macro/` to read what a release meant once
        it's out.

        The window is `[from_date, to_date)` — from inclusive, to exclusive;
        date-only values mean UTC midnight; defaults are today (UTC) → +7
        days; the span is capped at 400 days. No pagination: a full year of
        every series is ~250 rows (hard cap 500), ordered by `scheduled_at`
        ascending. Cancelled and postponed occurrences stay in the response
        with their `schedule_status`.
      parameters:
        - in: query
          name: from_date
          description: >-
            Window start, inclusive. `YYYY-MM-DD` (UTC midnight) or an ISO
            datetime (naive = UTC). Default: today, UTC midnight.
          schema: { type: string, example: "2026-08-07" }
        - in: query
          name: to_date
          description: >-
            Window end, exclusive. Same formats. Default `from_date` + 7 days;
            span capped at 400 days.
          schema: { type: string, example: "2026-08-14" }
        - in: query
          name: event_key
          description: Narrow to specific series, comma-separated. Unknown keys return 400.
          schema: { type: string, example: "cpi,nfp,fomc_decision" }
        - in: query
          name: importance
          description: Narrow by importance tier.
          schema: { type: string, enum: [high, medium, low] }
        - in: query
          name: country
          description: v1 covers US releases only.
          schema: { type: string, enum: [US], default: US }
      responses:
        "200":
          description: Occurrences within the window, `scheduled_at` ascending.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CalendarEvents" }
        "400":
          description: >-
            Unknown parameter or value, a malformed date, `to_date` not after
            `from_date`, or a window over 400 days.
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/news/{uid}/:
    get:
      tags: [news]
      summary: Get a news article by UID
      parameters:
        - in: path
          name: uid
          required: true
          schema: { type: string, pattern: "^[a-f0-9]{16}$" }
      responses:
        "200":
          description: A single enriched article.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RichNewsArticle" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/news/{uid}/related/:
    get:
      tags: [news]
      summary: Articles related to one article
      description: |
        Up to 6 articles related to the given article: fresh articles sharing
        a ticker first, then same-category recents as backfill. Same enriched
        shape and `relevance_score >= 4` filtering as `/api/news/`; insider
        (SEC Form 4) rows are excluded unless the base article is itself
        insider. No pagination — the list is a fixed-size block.
      parameters:
        - in: path
          name: uid
          required: true
          schema: { type: string, pattern: "^[a-f0-9]{16}$" }
      responses:
        "200":
          description: Related enriched articles (possibly fewer than 6).
          content:
            application/json:
              schema:
                type: object
                required: [results]
                properties:
                  results:
                    type: array
                    maxItems: 6
                    items: { $ref: "#/components/schemas/RichNewsArticle" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/symbols/:
    get:
      tags: [symbols]
      summary: List active tickers
      description: >-
        All active symbols (US equities, crypto, and foreign listings),
        alphabetical by ticker. ~10k entries — cache aggressively, or slice with
        the optional `limit`/`offset` params (the response stays a bare array
        either way). Pass `search` to resolve a name, brand or ticker prefix to
        its canonical symbol (`search=bitcoin` returns `BTC-USD`,
        `search=spacex` returns `SPCX`).
      parameters:
        - in: query
          name: limit
          description: Return at most this many symbols. Omit for the full list.
          schema: { type: integer, minimum: 1, maximum: 10000 }
        - in: query
          name: offset
          description: Skip this many symbols from the start of the list.
          schema: { type: integer, minimum: 0, default: 0 }
        - in: query
          name: search
          description: >-
            Resolve a query to matching symbols: ticker-prefix, company-name
            substring, or the company's brand name where that differs from its
            registered name (`search=spacex` returns `SPCX`, registered as
            SPACE EXPLORATION TECHNOLOGIES CORP). Case-insensitive; exact
            ticker, exact name and brand matches rank above prefix and
            substring matches. Useful for finding the canonical form of a name
            (`search=bitcoin` returns `BTC-USD`).
          schema: { type: string, example: "bitcoin" }
      responses:
        "200":
          description: List of active symbols.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Symbol" }
        "400": { description: Out-of-range or unknown query parameter. }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/symbols/{ticker}/:
    get:
      tags: [symbols]
      summary: Symbol detail
      parameters:
        - in: path
          name: ticker
          required: true
          description: >-
            Ticker symbol. US equities are bare (`AAPL`); cryptocurrencies use
            the `<SYM>-USD` form (`BTC-USD`); foreign listings use the Yahoo
            suffix (`VOD.L`).
          schema: { type: string, pattern: "^[A-Z0-9][A-Z0-9.\\-]{0,19}$", example: "BTC-USD" }
      responses:
        "200":
          description: >-
            Symbol with extended metadata. Resolves delisted symbols too:
            check `status`, and `renamed_to` for the successor ticker when
            the company continues under a new symbol.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Symbol" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/symbols/{ticker}/sentiment-summary/:
    get:
      tags: [symbols]
      summary: 7-day AI sentiment rollup for one ticker
      description: |
        Counts of bullish / neutral / bearish per-ticker sentiment calls from
        the enriched news flow over the trailing 7 days, plus per-day buckets.
        Insider (SEC Form 4) template rows are excluded — this reflects press
        coverage, not filings.
      parameters:
        - in: path
          name: ticker
          required: true
          description: >-
            Accepts the same ticker forms as the symbol detail endpoint: bare
            crypto names resolve to their `<SYM>-USD` listing (`DOGE` →
            `DOGE-USD`; the response echoes the resolved ticker), and delisted
            or renamed symbols stay addressable.
          schema: { type: string, pattern: "^[A-Z0-9][A-Z0-9.\\-]{0,19}$" }
      responses:
        "200":
          description: >-
            Sentiment counts for the window. Zeros mean a quiet week for a
            real listing; a ticker no symbol owns returns 404 instead.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TickerSentimentSummary" }
        "400": { description: Malformed ticker. }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/symbols/{ticker}/insider-summary/:
    get:
      tags: [symbols]
      summary: 30-day insider-transaction rollup for one ticker
      description: |
        Aggregated SEC Form 4 activity over the trailing 30 days: buy/sell
        transaction counts, dollar volumes, the share of transactions executed
        under pre-arranged 10b5-1 plans, and the most active insiders ranked
        by absolute net value (buys minus sells).
      parameters:
        - in: path
          name: ticker
          required: true
          description: >-
            Accepts the same ticker forms as the symbol detail endpoint: bare
            crypto names resolve to their `<SYM>-USD` listing, and delisted or
            renamed symbols stay addressable. Crypto and foreign listings have
            no Form 4 flow, so they return zeros.
          schema: { type: string, pattern: "^[A-Z0-9][A-Z0-9.\\-]{0,19}$" }
      responses:
        "200":
          description: >-
            Insider activity stats for the window. Zeros mean no filings in
            the window for a real listing; a ticker no symbol owns returns
            404 instead.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TickerInsiderSummary" }
        "400": { description: Malformed ticker. }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/symbols/{ticker}/insider-trades/:
    get:
      tags: [symbols]
      summary: Form 4 event history and chart aggregates for one ticker
      description: |
        The complete per-ticker insider-trading payload behind
        alphai.io/stock/{ticker}/insider-trades: the paginated Form 4 event
        history plus everything a chart needs, in one request. One event is
        a filing's whole tranche group (shares summed, the price
        value-weighted, a 10b5-1 ladder is ONE event), same folding as the
        feed's `insider` block.



# --- truncated at 32 KB (72 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/alphaai/refs/heads/main/openapi/alphaai-rest-api-openapi.yml