openapi: 3.0.3
info:
  title: QNews API
  version: 1.0.0
  description: Commercial news API with search, top stories, category feeds, resolved publisher URLs, and optional article-content extraction.
  contact:
    email: qnews@quantosei.com
servers:
  - url: https://qnews.quantosei.com
    description: Production
  - url: http://localhost:3017
    description: Local development
security:
  - ApiKeyAuth: []
  - BearerAuth: []
paths:
  /v1/news/search:
    get:
      summary: Search news
      description: Search Google News RSS results and return resolved publisher URLs.
      operationId: searchNews
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
          description: Search query.
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/IncludeContent'
        - $ref: '#/components/parameters/IncludeContentSnake'
        - $ref: '#/components/parameters/Hl'
        - $ref: '#/components/parameters/Gl'
        - $ref: '#/components/parameters/Ceid'
      responses:
        '200':
          description: News results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsResponse'
        '400':
          description: Missing query or invalid request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/SubscriptionRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /v1/news/top-stories:
    get:
      summary: Top stories
      description: Return the current Google News top stories feed for the requested locale.
      operationId: topStories
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/IncludeContent'
        - $ref: '#/components/parameters/IncludeContentSnake'
        - $ref: '#/components/parameters/Hl'
        - $ref: '#/components/parameters/Gl'
        - $ref: '#/components/parameters/Ceid'
      responses:
        '200':
          description: News results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/SubscriptionRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /v1/news/category/{categoryName}:
    get:
      summary: Category feed
      description: Return a category-specific news feed.
      operationId: categoryNews
      parameters:
        - name: categoryName
          in: path
          required: true
          schema:
            type: string
            enum:
              - world
              - nation
              - business
              - technology
              - entertainment
              - sports
              - science
              - health
          description: News category.
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/IncludeContent'
        - $ref: '#/components/parameters/IncludeContentSnake'
        - $ref: '#/components/parameters/Hl'
        - $ref: '#/components/parameters/Gl'
        - $ref: '#/components/parameters/Ceid'
      responses:
        '200':
          description: News results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsResponse'
        '400':
          description: Invalid category.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/SubscriptionRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /v1/news/categories:
    get:
      summary: List categories
      description: Return the available category names. This endpoint requires an API key and counts as one request.
      operationId: listCategories
      responses:
        '200':
          description: Available categories.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoriesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/SubscriptionRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        default: 25
      description: Requested article count. Values are capped by plan.
    IncludeContent:
      name: includeContent
      in: query
      required: false
      schema:
        type: string
        enum: ['true', 'false']
        default: 'false'
      description: Set to true to request article-content extraction. Pro, Business, and Enterprise only.
    IncludeContentSnake:
      name: include_content
      in: query
      required: false
      schema:
        type: string
        enum: ['true', 'false']
        default: 'false'
      description: Snake-case alias for includeContent.
    Hl:
      name: hl
      in: query
      required: false
      schema:
        type: string
        default: en
      description: Google News language parameter.
    Gl:
      name: gl
      in: query
      required: false
      schema:
        type: string
        default: US
      description: Google News country/region parameter.
    Ceid:
      name: ceid
      in: query
      required: false
      schema:
        type: string
      description: Google News edition identifier, commonly COUNTRY:LANGUAGE.
  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    SubscriptionRequired:
      description: Active subscription required.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Email verification, quota, or plan restriction.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Per-minute rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServerError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    Article:
      type: object
      required:
        - title
        - link
        - image
        - source
        - datetime
        - time
        - articleType
        - favicon
        - content
      properties:
        title:
          type: string
        link:
          type: string
          format: uri
          description: Resolved publisher URL when possible.
        image:
          type: string
          description: Image URL when available; otherwise an empty string.
        source:
          type: string
        datetime:
          type: string
          format: date-time
        time:
          type: string
        articleType:
          type: string
          example: regular
        favicon:
          type: string
          description: Publisher favicon URL when available; otherwise an empty string.
        content:
          type: string
          description: Article text when includeContent=true and extraction succeeds; otherwise an empty string.
    Usage:
      type: object
      required:
        - requestsThisMonth
        - contentUnitsThisMonth
        - monthlyRequestLimit
        - contentExtractionLimit
      properties:
        requestsThisMonth:
          type: integer
        contentUnitsThisMonth:
          type: integer
        monthlyRequestLimit:
          type: integer
        contentExtractionLimit:
          type: integer
    NewsMeta:
      type: object
      required:
        - count
        - includeContent
        - plan
        - usage
      properties:
        count:
          type: integer
        includeContent:
          type: boolean
        plan:
          type: string
        usage:
          $ref: '#/components/schemas/Usage'
    NewsResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Article'
        meta:
          $ref: '#/components/schemas/NewsMeta'
    CategoriesResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            type: string
            enum:
              - world
              - nation
              - business
              - technology
              - entertainment
              - sports
              - science
              - health
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          examples:
            - missing_api_key
            - invalid_api_key
            - subscription_required
            - email_not_verified
            - monthly_quota_exceeded
            - content_not_included
            - content_quota_exceeded
            - rate_limited
            - server_error
        message:
          type: string
        details:
          type: object
          additionalProperties: true
