openapi: 3.1.0
info:
  title: alphai REST API
  version: "1.0.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`). Anonymous traffic on the news routes is rate-limited
    to 60 req/min/IP. Authenticated traffic is metered hourly per account
    (10 / 100 / 1000 calls per hour for Free / Basic / Pro).

    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://alphai.io
    description: Production

security:
  - apiKey: []

tags:
  - name: news
    description: Relevance-scored, ticker-linked news.
  - name: symbols
    description: Active equity symbols (tickers).

paths:
  /api/news/:
    get:
      tags: [news]
      summary: List news (paginated)
      description: |
        Paginated feed of enriched articles. Default filter: `relevance_score >= 7`
        AND at least one active ticker. Pagination is page-based with a fixed
        page size of 10.
      parameters:
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
        - in: query
          name: symbol
          description: Filter to articles that mention this ticker.
          schema: { type: string, example: "NVDA" }
        - in: query
          name: category
          schema: { $ref: "#/components/schemas/NewsCategory" }
        - in: query
          name: min_relevance
          description: Override the default ≥7 threshold.
          schema: { type: integer, minimum: 1, maximum: 10 }
      responses:
        "200":
          description: A page of enriched articles.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NewsPagination" }
        "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 ranked by `relevance_score`.
      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/{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/symbols/:
    get:
      tags: [symbols]
      summary: List active tickers
      description: All active equity symbols. ~10k entries — cache aggressively.
      responses:
        "200":
          description: List of active symbols.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Symbol" }
        "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
          schema: { type: string, pattern: "^[A-Z][A-Z0-9.\\-]{0,9}$" }
      responses:
        "200":
          description: Symbol with extended metadata.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Symbol" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: ak_live_*
      description: |
        Token of the form `ak_live_<random>`. Issued from
        `/account/api-keys` on the website. Send as `Authorization: Bearer …`.

  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: |
        Hourly quota exceeded. The `Retry-After` header tells you how long to wait.
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: No item with that identifier.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    NewsCategory:
      type: string
      enum:
        - earnings
        - mergers_acquisitions
        - regulation
        - macro_economy
        - sector_analysis
        - technology
        - commodities
        - crypto
        - ipo
        - geopolitics
        - insider
        - other

    Sentiment:
      type: string
      enum: [positive, neutral, negative]

    Confidence:
      type: string
      enum: [high, medium, low]

    Actionability:
      type: string
      enum: [high, medium, low, negligible]

    Topic:
      type: object
      required: [topic, relevance]
      properties:
        topic: { type: string }
        relevance: { type: number, format: float }

    OriginalArticle:
      type: object
      description: The article as fetched from the source. `raw_text` is intentionally NOT exposed here.
      required: [uid, title, url, summary, time_published, source]
      properties:
        id: { type: integer }
        uid: { type: string, pattern: "^[a-f0-9]{16}$" }
        title: { type: string }
        url: { type: string, format: uri }
        time_published: { type: string, format: date-time }
        authors: { type: array, items: { type: string } }
        summary: { type: string, description: AI-generated summary safe to redistribute. }
        banner_image: { type: string, format: uri, nullable: true }
        source: { type: string }
        source_domain: { type: string }
        topics:
          type: array
          items: { $ref: "#/components/schemas/Topic" }
        tickers_sentiment:
          type: array
          items: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ImpactAnalysis:
      type: object
      properties:
        summary: { type: string }
        sentiment: { $ref: "#/components/schemas/Sentiment" }
        price_impact_prediction: { type: string }
        confidence: { $ref: "#/components/schemas/Confidence" }
        reasoning: { type: string }

    TickerAnalysis:
      type: object
      properties:
        ticker: { type: string }
        relevance_context: { type: string }
        impact_analysis: { $ref: "#/components/schemas/ImpactAnalysis" }

    NewsTradingValue:
      type: object
      properties:
        actionability_score: { $ref: "#/components/schemas/Actionability" }
        timing_relevance: { type: string }
        market_sentiment_alignment: { type: string }
        estimated_read_time: { type: string }

    AITradingInsights:
      type: object
      properties:
        ticker_analysis:
          type: array
          items: { $ref: "#/components/schemas/TickerAnalysis" }
        news_trading_value: { $ref: "#/components/schemas/NewsTradingValue" }

    EnrichedArticle:
      type: object
      properties:
        category: { $ref: "#/components/schemas/NewsCategory" }
        relevance_score:
          type: integer
          minimum: 1
          maximum: 10
          description: |
            LLM-assigned market relevance:
            1–3 minor noise · 4–6 sector context · 7–8 actionable · 9–10 market-moving.
        ai_trading_insights: { $ref: "#/components/schemas/AITradingInsights" }

    RichNewsArticle:
      type: object
      required: [original, enrichment]
      properties:
        original: { $ref: "#/components/schemas/OriginalArticle" }
        enrichment: { $ref: "#/components/schemas/EnrichedArticle" }

    NewsPagination:
      type: object
      required: [page, has_more, results]
      properties:
        page: { type: integer }
        has_more: { type: boolean }
        results:
          type: array
          items: { $ref: "#/components/schemas/RichNewsArticle" }

    Symbol:
      type: object
      required: [symbol, name]
      properties:
        symbol: { type: string }
        name: { type: string }
        asset_type: { type: string, example: equity }
        sector: { type: string }
        industry: { type: string }
        description: { type: string }
        website: { type: string, format: uri, nullable: true }

    Error:
      type: object
      properties:
        message: { type: string }
        error: { type: string }
        detail: { type: string }
