openapi: 3.0.3
info:
  title: Scholar Sidekick API (RapidAPI)
  version: 1.0.15
  description: |
    **Scholar Sidekick** resolves scholarly identifiers — DOIs, PMIDs/PMCIDs,
    ISBNs, ISSN/eISSNs, arXiv IDs, NASA ADS bibcodes, and WHO IRIS URLs — into
    normalized metadata and clean citations. Batch input is supported. Export
    directly to **RIS** or **BibTeX** for use in Zotero, EndNote, or Mendeley.

    **Endpoints**
    - `POST /api/format` — Resolve and format citations (JSON). Recommended for RapidAPI.
    - `POST /api/export` — Export citations to **RIS** or **BibTeX**.
    - `GET /api/health` — Liveness check.
    _All non-health endpoints are POST-only._

    **Auth**
    - The RapidAPI gateway injects `X-RapidAPI-Key` automatically (declared via `securitySchemes` below).
      Do **not** add this header manually in requests or code samples.
    - RapidAPI subscription tier is conveyed via `X-RapidAPI-Subscription` and mapped to an internal plan
      (`basic`, `free`, `pro`, `ultra`, `mega`) exposed as `X-Auth-Plan`.

    **Rate Limits**
    - Per-plan monthly quotas (RapidAPI) and per-minute burst limits (Scholar Sidekick).
    - On `429`, responses include `Retry-After`. Successful responses include `X-RateLimit-*` and `RateLimit-*`.

    **RapidAPI Quotas (per plan)**
    | Plan  | Burst/min | Monthly cap |
    |------ | --------- | ----------- |
    | Basic | 10        | 500         |
    | Pro   | 60        | 10,000      |
    | Ultra | 300       | 100,000     |
    | Mega  | 1,000     | 500,000     |

    *Burst limits are enforced by Scholar Sidekick (**per 60s sliding window**). Monthly caps are enforced by RapidAPI.*

    **CSL Styles**
    - If CSL import is disabled, unknown/custom styles return **400** or fallback to **Vancouver**
      with `X-CSL-Warning: fallback-to-default`, per configured policy.

    **Headers**
    - Responses include **both** legacy and IETF rate-limit headers:
      - Legacy: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-RateLimit-Policy`
      - IETF: `RateLimit-Limit: <n>;w=60`, `RateLimit-Remaining`, `RateLimit-Reset`
    - Caching: `Vary` includes `X-RapidAPI-Key`, `X-RapidAPI-Subscription`, and `X-Scholar-Plan`.

  termsOfService: https://scholar-sidekick.com/legal/api-terms
  license:
    name: Proprietary
    url: https://scholar-sidekick.com/legal/api-terms

servers:
  - url: https://scholar-sidekick.p.rapidapi.com
    description: RapidAPI gateway → forwards to Scholar Sidekick

security:
  - RapidApiKey: []

paths:
  /api/health:
    get:
      summary: Health check
      operationId: healthCheck
      security: [] # public
      responses:
        "200":
          description: OK
          headers:
            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" }
            Cache-Control: { $ref: "#/components/headers/Cache-Control-NoStore" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OkPayload" }
              example: { ok: true }
        "400": { $ref: "#/components/responses/BadRequest" }
        "503": { $ref: "#/components/responses/Maintenance" }

  /api/format:
    post:
      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: format
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FormatRequest" }
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://scholar-sidekick.p.rapidapi.com/api/format \
              -H 'Content-Type: application/json' \
              -H 'X-RapidAPI-Subscription: Basic' \
              -d '{"text":"10.1056/nejmoa2033700","style":"vancouver","output":"text"}'
        - lang: JavaScript
          label: fetch
          source: |
            const res = await fetch("https://scholar-sidekick.p.rapidapi.com/api/format", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-RapidAPI-Subscription": "Basic"
              },
              body: JSON.stringify({
                text: "10.1056/nejmoa2033700",
                style: "vancouver",
                output: "text"
              })
            });
            const data = await res.json();
            console.log(data);
      responses:
        "200":
          description: OK
          headers:
            Vary: { $ref: "#/components/headers/Vary" }
            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" }
            RateLimit-Limit: { $ref: "#/components/headers/RateLimit-Limit" }
            RateLimit-Remaining: { $ref: "#/components/headers/RateLimit-Remaining" }
            RateLimit-Reset: { $ref: "#/components/headers/RateLimit-Reset" }
            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" }
        "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/export:
    post:
      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: export
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ExportRequest" }
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://scholar-sidekick.p.rapidapi.com/api/export \
              -H 'Content-Type: application/json' \
              -H 'X-RapidAPI-Subscription: Pro' \
              -d '{"text":"10.1056/nejmoa2033700","style":"vancouver","format":"ris"}'
        - lang: JavaScript
          label: fetch
          source: |
            const res = await fetch("https://scholar-sidekick.p.rapidapi.com/api/export", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-RapidAPI-Subscription": "Pro"
              },
              body: JSON.stringify({
                text: "10.1056/nejmoa2033700",
                style: "vancouver",
                format: "ris"
              })
            });
            const data = await res.json();
            console.log(data);
      responses:
        "200":
          description: OK
          headers:
            Vary: { $ref: "#/components/headers/Vary" }
            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" }
            RateLimit-Limit: { $ref: "#/components/headers/RateLimit-Limit" }
            RateLimit-Remaining: { $ref: "#/components/headers/RateLimit-Remaining" }
            RateLimit-Reset: { $ref: "#/components/headers/RateLimit-Reset" }
            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" }
        "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" }

components:
  securitySchemes:
    RapidApiKey:
      type: apiKey
      in: header
      name: X-RapidAPI-Key
      description: RapidAPI marketplace key injected by RapidAPI gateway. Do not add manually.

  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 (as resolved from RapidAPI or first-party).
      schema:
        type: string
        enum: [free, pro, ultra, mega, enterprise, anonymous]
    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] }

    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;type=sliding").
      schema: { type: string }

    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"] }
    Vary:
      description: Cache key hints; includes auth/plan headers for RapidAPI.
      schema: { type: string }
    RateLimit-Limit:
      description: IETF consolidated limit for the current window (e.g., "60;w=60;type=sliding").
      schema: { type: string }
    RateLimit-Remaining:
      description: IETF remaining requests in the current window.
      schema: { type: integer, minimum: 0 }
    RateLimit-Reset:
      description: IETF seconds until the window resets.
      schema: { type: integer, minimum: 0 }

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

    FormatRequest:
      type: object
      additionalProperties: false
      description: |
        Submit exactly one of: `text`, `lines`, or `items`.
        Studio note: using a single schema (no oneOf) improves RapidAPI form rendering.
      properties:
        text:
          type: string
          description: Single raw input string containing identifiers.
        lines:
          type: array
          minItems: 1
          items: { type: string }
          description: Array of identifiers (DOI, PMID, ISBN, etc.).
        items:
          type: array
          minItems: 1
          items:
            type: object
            description: "CSL-JSON item"
        style:
          type: string
          description: Citation style id (builtin or CSL id).
        output:
          type: string
          description: Output mode.
          enum: [json, text]
      example:
        text: "10.1056/nejmoa2033700"
        style: "vancouver"
        output: "text"

    ExportRequest:
      type: object
      additionalProperties: false
      description: |
        Submit `text` or `items` and select a `format`.
        Studio note: single schema improves RapidAPI form rendering.
      properties:
        text:
          type: string
          description: Single raw input string containing identifiers.
        items:
          type: array
          minItems: 1
          items:
            type: object
            description: "CSL-JSON item"
        style:
          type: string
          description: Citation style id (builtin or CSL id).
        format:
          type: string
          description: Export format.
          enum: [txt, ris, bibtex, csl-json, endnote-xml, refworks, nbib, rdf, csv]
      example:
        text: "10.1056/nejmoa2033700"
        style: "vancouver"
        format: "ris"

    OkPayload:
      type: object
      description: Successful response payload (route-specific).
      additionalProperties: true

  responses:
    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" }
