openapi: 3.1.0
info:
  title: Scholar Sidekick API
  version: 1.0.4
  description: |
    Resolve scholarly identifiers (DOI, PMID, PMCID, ISBN, ISSN/eISSN, arXiv, ADS bibcode, scholarly URLs)
    into normalized metadata and formatted citations. Supports export to RIS and BibTeX.
  termsOfService: https://scholar-sidekick.com/legal/api-terms
  contact:
    name: Scholar Sidekick
    email: support@scholar-sidekick.com
    url: https://scholar-sidekick.com
  x-logo:
    url: https://scholar-sidekick.com/icon-512.png
    altText: Scholar Sidekick
  license:
    name: Apache-2.0
    identifier: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0

servers:
  - url: https://scholar-sidekick.com
    description: Production

tags:
  - name: Health
    description: Service status and diagnostic endpoints.
  - name: Format
    description: Resolve identifiers and format citations.
  - name: Export
    description: Export citations to manager-friendly formats.

security:
  - ScholarApiKey: []
  - RapidApiKey: []

paths:
  /api/health:
    get:
      tags: [Health]
      summary: Health check
      operationId: healthCheck
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
            X-Auth-Source: { $ref: "#/components/headers/X-Auth-Source" }
            X-Auth-Plan: { $ref: "#/components/headers/X-Auth-Plan" }
            Content-Security-Policy: { $ref: "#/components/headers/Content-Security-Policy" }
            X-Content-Type-Options: { $ref: "#/components/headers/X-Content-Type-Options" }
            X-Frame-Options: { $ref: "#/components/headers/X-Frame-Options" }
            Referrer-Policy: { $ref: "#/components/headers/Referrer-Policy" }
            Permissions-Policy: { $ref: "#/components/headers/Permissions-Policy" }
            Cross-Origin-Opener-Policy: { $ref: "#/components/headers/Cross-Origin-Opener-Policy" }
            Cross-Origin-Resource-Policy:
              { $ref: "#/components/headers/Cross-Origin-Resource-Policy" }
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  time: { type: string, format: date-time }
        "400":
          description: Bad request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
              examples:
                badInput:
                  value: { ok: false, code: BAD_REQUEST, error: "Invalid input" }
        "503":
          $ref: "#/components/responses/Maintenance"

  /api/format:
    post:
      tags: [Format]
      summary: Resolve & format identifiers (non-streaming)
      description: |
        Accepts a list of identifiers or a single free-text string. Resolves to metadata, deduplicates,
        and formats citations in the requested style. Returns JSON with text or HTML output.
      operationId: formatCreate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FormatRequest" }
            examples:
              linesExample:
                summary: Batch lines
                value:
                  lines: ["10.1056/nejmoa2033700", "PMID: 30049270", "ISBN: 9780192854087"]
                  style: vancouver
                  output: text
              textExample:
                summary: Single text
                value:
                  text: "10.1056/nejmoa2033700"
                  style: vancouver
                  output: text
      responses:
        "200":
          $ref: "#/components/responses/OkJsonWithRL"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Maintenance"

  /api/format-items:
    post:
      tags: [Format]
      summary: Format pre-parsed items (CSL-JSON input)
      description: |
        Accepts pre-parsed items in CSL-JSON form (e.g., results from a prior resolve step)
        and formats citations without re-resolving identifiers.
      operationId: formatItemsCreate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FormatItemsRequest" }
      responses:
        "200":
          $ref: "#/components/responses/OkJsonWithRL"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Maintenance"

  /api/format/stream:
    post:
      tags: [Format]
      summary: Resolve & format identifiers (Server-Sent Events)
      description: |
        Streams incremental formatting output via **text/event-stream**.
        If streaming is disabled by policy, returns 403.
      operationId: formatStreamCreate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [lines]
              properties:
                lines:
                  type: array
                  items: { type: string }
                style:
                  type: string
                  example: vancouver
      responses:
        "200":
          description: OK (SSE stream)
          headers:
            X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
            X-Auth-Source: { $ref: "#/components/headers/X-Auth-Source" }
            X-Auth-Plan: { $ref: "#/components/headers/X-Auth-Plan" }
            X-Auth-Subject: { $ref: "#/components/headers/X-Auth-Subject" }
            X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
            X-RateLimit-Reset-After: { $ref: "#/components/headers/X-RateLimit-Reset-After" }
            X-RateLimit-Policy: { $ref: "#/components/headers/X-RateLimit-Policy" }
            Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
            Content-Security-Policy: { $ref: "#/components/headers/Content-Security-Policy" }
            X-Content-Type-Options: { $ref: "#/components/headers/X-Content-Type-Options" }
            X-Frame-Options: { $ref: "#/components/headers/X-Frame-Options" }
            Referrer-Policy: { $ref: "#/components/headers/Referrer-Policy" }
            Permissions-Policy: { $ref: "#/components/headers/Permissions-Policy" }
            Cross-Origin-Opener-Policy: { $ref: "#/components/headers/Cross-Origin-Opener-Policy" }
            Cross-Origin-Resource-Policy:
              { $ref: "#/components/headers/Cross-Origin-Resource-Policy" }
          content:
            text/event-stream:
              schema:
                type: string
                description: Server-Sent Events stream
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Forbidden (read-only mode or streaming disabled)
          headers:
            X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
            X-Scholar-Mode: { $ref: "#/components/headers/X-Scholar-Mode" }
            X-Error-Code: { $ref: "#/components/headers/X-Error-Code" }
            Content-Security-Policy: { $ref: "#/components/headers/Content-Security-Policy" }
            X-Content-Type-Options: { $ref: "#/components/headers/X-Content-Type-Options" }
            X-Frame-Options: { $ref: "#/components/headers/X-Frame-Options" }
            Referrer-Policy: { $ref: "#/components/headers/Referrer-Policy" }
            Permissions-Policy: { $ref: "#/components/headers/Permissions-Policy" }
            Cross-Origin-Opener-Policy: { $ref: "#/components/headers/Cross-Origin-Opener-Policy" }
            Cross-Origin-Resource-Policy:
              { $ref: "#/components/headers/Cross-Origin-Resource-Policy" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
              examples:
                readOnly:
                  value: { ok: false, code: READ_ONLY, error: "Read-only mode" }
                streamingDisabled:
                  value: { ok: false, code: STREAMING_DISABLED, error: "Streaming disabled" }
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Maintenance"

  /api/export:
    post:
      tags: [Export]
      summary: Export citations in a target format
      description: |
        Exports resolved citations as **RIS** or **BibTeX** for easy import into reference managers.
        Input may be raw identifiers (`text`) or pre-parsed items.
      operationId: exportCreate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ExportRequest" }
            examples:
              textExample:
                summary: Single identifier
                value:
                  text: "10.1056/nejmoa2033700"
                  style: vancouver
                  format: ris
              itemsExample:
                summary: Pre-parsed CSL-JSON items
                value:
                  items: [{ "title": "Example title", "type": "article-journal" }]
                  style: ama
                  format: bibtex
      responses:
        "200":
          description: Exported file
          content:
            text/plain:
              schema: { type: string }
        "400": { description: Bad request }
        "401": { description: Unauthorized }
        "429": { description: Rate limit exceeded }

components:
  securitySchemes:
    ScholarApiKey:
      type: apiKey
      in: header
      name: X-Scholar-API-Key
    RapidApiKey:
      type: apiKey
      in: header
      name: X-RapidAPI-Key
      description: RapidAPI marketplace key forwarded by RapidAPI gateway.

  headers:
    X-Request-Id:
      description: Unique request correlation ID.
      schema: { type: string }
    X-Auth-Source:
      description: Where the auth came from (first-party, rapidapi, anonymous).
      schema: { type: string, enum: [first-party, rapidapi, anonymous] }
    X-Auth-Plan:
      description: Effective plan for this request (Free/Developer/Pro/Enterprise).
      schema: { type: string }
    X-Auth-Subject:
      description: Subject/account identifier (user id, key hash, etc.).
      schema: { type: string }
    X-Error-Code:
      description: Stable internal error code.
      schema: { type: string }
    X-Scholar-Mode:
      description: Effective service mode (normal, read-only, maintenance).
      schema: { type: string, enum: [normal, read-only, maintenance] }

    # Rate-limit family
    X-RateLimit-Limit:
      description: Maximum requests allowed in the current window.
      schema: { type: integer, minimum: 0 }
    X-RateLimit-Remaining:
      description: Requests remaining in the current window.
      schema: { type: integer, minimum: 0 }
    X-RateLimit-Reset:
      description: UTC epoch seconds when the window resets.
      schema: { type: integer, minimum: 0 }
    X-RateLimit-Reset-After:
      description: Seconds until reset.
      schema: { type: integer, minimum: 0 }
    X-RateLimit-Policy:
      description: Policy string (e.g., "60;w=60, 1000;w=3600").
      schema: { type: string }

    # Cache/controls and security headers
    Cache-Control-NoStore:
      description: Instructs clients not to cache.
      schema: { type: string, enum: ["no-store"] }
    Content-Security-Policy:
      description: CSP controlling resource loading.
      schema: { type: string }
    X-Content-Type-Options:
      description: MIME type sniffing prevention.
      schema: { type: string, enum: ["nosniff"] }
    X-Frame-Options:
      description: Clickjacking protection.
      schema: { type: string, enum: ["DENY", "SAMEORIGIN"] }
    Referrer-Policy:
      description: Referrer information policy.
      schema:
        type: string
        enum:
          [
            "no-referrer",
            "no-referrer-when-downgrade",
            "origin",
            "origin-when-cross-origin",
            "same-origin",
            "strict-origin",
            "strict-origin-when-cross-origin",
            "unsafe-url",
          ]
    Permissions-Policy:
      description: Feature policy controls.
      schema: { type: string }
    Cross-Origin-Opener-Policy:
      description: Cross-origin opener policy.
      schema: { type: string, enum: ["same-origin", "same-origin-allow-popups", "unsafe-none"] }
    Cross-Origin-Resource-Policy:
      description: Cross-origin resource policy.
      schema: { type: string, enum: ["same-origin", "same-site", "cross-origin"] }

  schemas:
    # Shared error envelope
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [ok, code, error]
      properties:
        ok: { type: boolean, enum: [false] }
        code: { type: string }
        error: { type: string }

    # /api/format request
    FormatRequest:
      oneOf:
        - type: object
          additionalProperties: false
          required: [text]
          properties:
            text: { type: string }
            style: { type: string, description: "Citation style id (builtin or CSL id)" }
            output:
              type: string
              description: Output mode.
              enum: [json, text]
        - type: object
          additionalProperties: false
          required: [lines]
          properties:
            lines:
              type: array
              minItems: 1
              items: { type: string }
            style: { type: string }
            output:
              type: string
              enum: [json, text]

    # /api/format-items request
    FormatItemsRequest:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          minItems: 1
          items:
            type: object
            description: "CSL-JSON item"
        style: { type: string }
        output:
          type: string
          enum: [json, text]

    # /api/export request
    ExportRequest:
      oneOf:
        - type: object
          additionalProperties: false
          required: [text, format]
          properties:
            text: { type: string }
            style: { type: string }
            format:
              type: string
              description: Export format.
              enum: [txt, ris, bibtex, csl-json, endnote-xml, refworks, nbib, rdf, csv]
        - type: object
          additionalProperties: false
          required: [items, format]
          properties:
            items:
              type: array
              minItems: 1
              items:
                type: object
                description: "CSL-JSON item"
            style: { type: string }
            format:
              type: string
              enum: [txt, ris, bibtex, csl-json, endnote-xml, refworks, nbib, rdf, csv]

    # Typical OK envelope (shape varies by route; keep loose)
    OkPayload:
      type: object
      description: Successful response payload (route-specific).
      additionalProperties: true

  responses:
    OkJsonWithRL:
      description: OK
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-Auth-Source: { $ref: "#/components/headers/X-Auth-Source" }
        X-Auth-Plan: { $ref: "#/components/headers/X-Auth-Plan" }
        X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
        X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
        X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
        X-RateLimit-Reset-After: { $ref: "#/components/headers/X-RateLimit-Reset-After" }
        X-RateLimit-Policy: { $ref: "#/components/headers/X-RateLimit-Policy" }
        Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
        Content-Security-Policy: { $ref: "#/components/headers/Content-Security-Policy" }
        X-Content-Type-Options: { $ref: "#/components/headers/X-Content-Type-Options" }
        X-Frame-Options: { $ref: "#/components/headers/X-Frame-Options" }
        Referrer-Policy: { $ref: "#/components/headers/Referrer-Policy" }
        Permissions-Policy: { $ref: "#/components/headers/Permissions-Policy" }
        Cross-Origin-Opener-Policy: { $ref: "#/components/headers/Cross-Origin-Opener-Policy" }
        Cross-Origin-Resource-Policy: { $ref: "#/components/headers/Cross-Origin-Resource-Policy" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/OkPayload" }

    BadRequest:
      description: Bad Request
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-Error-Code: { $ref: "#/components/headers/X-Error-Code" }
        Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }

    Unauthorized:
      description: Unauthorized
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-Error-Code: { $ref: "#/components/headers/X-Error-Code" }
        Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }

    Forbidden:
      description: Forbidden
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-Scholar-Mode: { $ref: "#/components/headers/X-Scholar-Mode" }
        X-Error-Code: { $ref: "#/components/headers/X-Error-Code" }
        Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }

    TooManyRequests:
      description: Too Many Requests
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
        X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
        X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
        X-RateLimit-Reset-After: { $ref: "#/components/headers/X-RateLimit-Reset-After" }
        X-RateLimit-Policy: { $ref: "#/components/headers/X-RateLimit-Policy" }
        Retry-After:
          description: Seconds until next request allowed.
          schema: { type: integer, minimum: 0 }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }

    Maintenance:
      description: Service temporarily unavailable (maintenance mode).
      headers:
        X-Request-Id: { $ref: "#/components/headers/X-Request-Id" }
        X-Scholar-Mode: { $ref: "#/components/headers/X-Scholar-Mode" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
