openapi: 3.0.3
info:
  title: GPTBased Leaderboard
  version: "1.0.0"
  description: |
    Combined LMArena Elo rankings and OpenRouter pricing across 8 categories
    (text, webdev, vision, text-to-image, image-edit, text-to-video,
    image-to-video, video-edit). Adds daily historical snapshots, weekly
    movers, per-model detail with provider endpoints, and Wavespeed inference
    URLs for image and video variants.

    All endpoints are read-only and return JSON. Cache TTL is 12 hours
    (24 hours on /meta). Snapshots refresh daily; rankings stay stable
    intra-day.

servers:
  - url: https://gptbased.com
    description: Production origin (RapidAPI routes here)

tags:
  - name: Discovery
    description: Surface metadata
  - name: Browse
    description: Paginated lists
  - name: Picks
    description: Curated recommendations
  - name: Detail
    description: Single-model lookups
  - name: Trends
    description: Historical snapshots and movement

paths:

  /api/rapid/v1/meta:
    get:
      tags: [Discovery]
      summary: List supported categories, slugs, and limits
      description: |
        Returns the valid `category` values, `best_for` slugs, universal
        request-size caps, and the full endpoint catalog. Call this first
        from any client to discover what the API supports without burning
        quota on intentional 400 responses.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MetaResponse"
              example:
                categories: [text, webdev, vision, text_to_image, image_edit, text_to_video, image_to_video, video_edit]
                best_for_slugs: [coding, multilingual, cheap]
                limits:
                  max_limit: 500
                  max_days: 365
                  max_compare_ids: 10
                endpoints:
                  - path: /leaderboard
                    params: [category, limit, offset, free]
                  - path: /picks
                    params: [category]
                  - path: /search
                    params: [category, free, min_elo, max_prompt_per_m_usd, max_completion_per_m_usd, min_context_length, input_modality, output_modality, limit, offset]
                  - path: /compare
                    params: [ids]
                  - path: /model
                    params: [id]
                  - path: /history
                    params: [id, category, days]
                  - path: /movers
                    params: [category, days, limit]
                  - path: /best_for/{slug}
                    params: [limit]

  /api/rapid/v1/leaderboard:
    get:
      tags: [Browse]
      summary: Paginated leaderboard for a category
      description: |
        Returns ranked models for one category. Includes OpenRouter-matched
        rows (with `slug`) and arena-only rows (with `slug: null`). The
        `free` filter excludes arena-only rows since they have no pricing
        data to mark as free vs paid.
      parameters:
        - $ref: "#/components/parameters/Category"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - name: free
          in: query
          description: |
            `true` keeps only OpenRouter `:free` variants. `false` keeps only
            paid OR variants (excludes arena-only). Omit for everything.
          required: false
          schema:
            type: boolean
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeaderboardResponse"
              example:
                category: text
                free: null
                total: 375
                limit: 3
                offset: 0
                models:
                  - rank: 1
                    name: "Anthropic: Claude Opus 4.6"
                    slug: anthropic/claude-opus-4.6
                    arena_model_name: claude-opus-4-6-thinking
                    variant: thinking
                    harness: null
                    elo: 1503.46
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    is_or: true
                    wavespeed: null
                  - rank: 2
                    name: "Anthropic: Claude Opus 4.7"
                    slug: anthropic/claude-opus-4.7
                    arena_model_name: claude-opus-4-7-thinking
                    variant: thinking
                    harness: null
                    elo: 1500.16
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    is_or: true
                    wavespeed: null
                  - rank: 3
                    name: "Anthropic: Claude Opus 4.6"
                    slug: anthropic/claude-opus-4.6
                    arena_model_name: claude-opus-4-6
                    variant: base
                    harness: null
                    elo: 1498.21
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    is_or: true
                    wavespeed: null
        "400":
          $ref: "#/components/responses/BadRequest"

  /api/rapid/v1/picks:
    get:
      tags: [Picks]
      summary: Editorial picks for a category
      description: |
        Returns three picks for one category: `top_ranked` (highest Elo
        OR-matched), `best_value` (Pareto knee in cost/rating space), and
        `recommended` (highest-rated frontier point at-or-below median
        frontier cost). Same algorithm the marketing leaderboard uses.
      parameters:
        - $ref: "#/components/parameters/Category"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PicksResponse"
              example:
                category: text
                top_ranked:
                  name: "Anthropic: Claude Opus 4.6"
                  slug: anthropic/claude-opus-4.6
                  arena_model_name: claude-opus-4-6-thinking
                  rank: 1
                  elo: 1503.46
                  prompt_per_m_usd: 5
                  completion_per_m_usd: 25
                  image_per_unit_usd: null
                  wavespeed: null
                best_value:
                  name: "Xiaomi: MiMo-V2.5-Pro"
                  slug: xiaomi/mimo-v2.5-pro
                  arena_model_name: mimo-v2.5-pro
                  rank: 27
                  elo: 1464.07
                  prompt_per_m_usd: 0.435
                  completion_per_m_usd: 0.87
                  image_per_unit_usd: null
                  wavespeed: null
                recommended:
                  name: "DeepSeek: DeepSeek V4 Flash"
                  slug: deepseek/deepseek-v4-flash
                  arena_model_name: deepseek-v4-flash-thinking
                  rank: 59
                  elo: 1436.09
                  prompt_per_m_usd: 0.0983
                  completion_per_m_usd: 0.1966
                  image_per_unit_usd: null
                  wavespeed: null
        "400":
          $ref: "#/components/responses/BadRequest"

  /api/rapid/v1/search:
    get:
      tags: [Browse]
      summary: Filtered leaderboard query
      description: |
        Filter the leaderboard by combinations of Elo, pricing, context
        length, and modalities. All filters AND-combine. Arena-only rows are
        excluded (no pricing or context_length to filter on).
      parameters:
        - $ref: "#/components/parameters/Category"
        - name: free
          in: query
          schema: { type: boolean }
        - name: min_elo
          in: query
          schema: { type: number, example: 1300 }
        - name: max_prompt_per_m_usd
          in: query
          description: Cap on prompt-token price ($/M tokens).
          schema: { type: number, example: 2 }
        - name: max_completion_per_m_usd
          in: query
          schema: { type: number }
        - name: min_context_length
          in: query
          schema: { type: integer, example: 200000 }
        - name: input_modality
          in: query
          description: Single modality token, e.g. `image`, `file`, `audio`.
          schema: { type: string, example: image }
        - name: output_modality
          in: query
          schema: { type: string }
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
              example:
                category: text
                filters:
                  free: null
                  min_elo: 1200
                  max_prompt_per_m_usd: null
                  max_completion_per_m_usd: null
                  min_context_length: null
                  input_modality: null
                  output_modality: null
                total: 146
                limit: 2
                offset: 0
                models:
                  - rank: 1
                    name: "Anthropic: Claude Opus 4.6"
                    slug: anthropic/claude-opus-4.6
                    arena_model_name: claude-opus-4-6-thinking
                    variant: thinking
                    harness: null
                    elo: 1503.46
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    context_length: 1000000
                    input_modalities: [text, image, file]
                    output_modalities: [text]
                  - rank: 2
                    name: "Anthropic: Claude Opus 4.7"
                    slug: anthropic/claude-opus-4.7
                    arena_model_name: claude-opus-4-7-thinking
                    variant: thinking
                    harness: null
                    elo: 1500.16
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    context_length: 1000000
                    input_modalities: [text, image, file]
                    output_modalities: [text]
        "400":
          $ref: "#/components/responses/BadRequest"

  /api/rapid/v1/compare:
    get:
      tags: [Detail]
      summary: Side-by-side comparison for multiple OpenRouter ids
      description: |
        Returns identity, modalities, pricing, and every overall placement
        for each requested OR id. Preserves requested order so a comparison
        UI can render a wide table without re-sorting.
      parameters:
        - name: ids
          in: query
          required: true
          description: |
            Comma-separated OpenRouter model ids. Capped at 10; extras are
            silently truncated and `truncated: true` is set on the response.
          schema:
            type: string
            example: anthropic/claude-opus-4.6,openai/gpt-5
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompareResponse"
              example:
                requested:
                  - anthropic/claude-opus-4.6
                  - anthropic/claude-opus-4.7
                cap: 10
                truncated: false
                missing: []
                models:
                  - slug: anthropic/claude-opus-4.6
                    name: "Anthropic: Claude Opus 4.6"
                    description: "Opus 4.6 is Anthropic's strongest model for coding and long-running professional tasks."
                    context_length: 1000000
                    input_modalities: [text, image, file]
                    output_modalities: [text]
                    pricing:
                      prompt: "0.000005"
                      completion: "0.000025"
                      web_search: "0.01"
                      input_cache_read: "0.0000005"
                      input_cache_write: "0.00000625"
                    placements:
                      - category: text
                        rank: 1
                        elo: 1503.46
                        variant: thinking
                        harness: null
                      - category: webdev
                        rank: 3
                        elo: 1542.37
                        variant: thinking
                        harness: null
                    wavespeed_variant_count: 0
                  - slug: anthropic/claude-opus-4.7
                    name: "Anthropic: Claude Opus 4.7"
                    description: "Opus 4.7 is the next generation of Anthropic's Opus family, built for long-running, asynchronous agents."
                    context_length: 1000000
                    input_modalities: [text, image, file]
                    output_modalities: [text]
                    pricing:
                      prompt: "0.000005"
                      completion: "0.000025"
                      web_search: "0.01"
                      input_cache_read: "0.0000005"
                      input_cache_write: "0.00000625"
                    placements:
                      - category: text
                        rank: 2
                        elo: 1500.16
                        variant: thinking
                        harness: null
                      - category: webdev
                        rank: 1
                        elo: 1566.22
                        variant: thinking
                        harness: null
                    wavespeed_variant_count: 0
        "400":
          $ref: "#/components/responses/BadRequest"

  /api/rapid/v1/model:
    get:
      tags: [Detail]
      summary: Full detail for one OpenRouter model
      description: |
        Returns identity, modalities, pricing, every leaderboard placement
        (subset, category, variant, harness, rank, Elo), and the per-provider
        endpoint list with uptime, latency, throughput, and regional pricing.

        `id` is a query param (not a path segment) because OR ids contain
        slashes.
      parameters:
        - name: id
          in: query
          required: true
          description: OpenRouter model id (e.g. `anthropic/claude-opus-4.6`).
          schema:
            type: string
            example: anthropic/claude-opus-4.6
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelDetailResponse"
              example:
                slug: anthropic/claude-opus-4.6
                name: "Anthropic: Claude Opus 4.6"
                canonical_slug: anthropic/claude-4.6-opus-20260205
                description: "Opus 4.6 is Anthropic's strongest model for coding and long-running professional tasks."
                context_length: 1000000
                input_modalities: [text, image, file]
                output_modalities: [text]
                pricing:
                  prompt: "0.000005"
                  completion: "0.000025"
                  web_search: "0.01"
                  input_cache_read: "0.0000005"
                  input_cache_write: "0.00000625"
                leaderboard:
                  - subset: text
                    category: chinese
                    variant: thinking
                    harness: null
                    rank: 1
                    elo: 1548.09
                    vote_count: 2091
                  - subset: text
                    category: coding
                    variant: thinking
                    harness: null
                    rank: 1
                    elo: 1552.68
                    vote_count: 9577
                providers:
                  - name: "Amazon Bedrock"
                    tag: amazon-bedrock/us-east-1
                    context_length: 1000000
                    quantization: unknown
                    status: 0
                    uptime_last_1d: 99.93
                    latency_last_30m: null
                    throughput_last_30m: null
                    pricing:
                      prompt: "0.000005"
                      completion: "0.000025"
                      web_search: "0.01"
                      input_cache_read: "0.0000005"
                      input_cache_write: "0.00000625"
                      discount: 0
                  - name: "Anthropic"
                    tag: anthropic
                    context_length: 1000000
                    quantization: unknown
                    status: 0
                    uptime_last_1d: 97.83
                    latency_last_30m: null
                    throughput_last_30m: null
                    pricing:
                      prompt: "0.000005"
                      completion: "0.000025"
                      web_search: "0.01"
                      input_cache_read: "0.0000005"
                      input_cache_write: "0.00000625"
                      discount: 0
                partners:
                  wavespeed: null
        "404":
          $ref: "#/components/responses/NotFound"

  /api/rapid/v1/history:
    get:
      tags: [Trends]
      summary: Daily rank and Elo history for one model
      description: |
        One series per (variant, harness) pair so reasoning modes stay
        separated. `days` is capped at 365.
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
            example: anthropic/claude-opus-4.6
        - name: category
          in: query
          required: false
          description: Defaults to `text`.
          schema:
            $ref: "#/components/schemas/Category"
        - $ref: "#/components/parameters/Days"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HistoryResponse"
              example:
                slug: anthropic/claude-opus-4.6
                category: text
                days: 30
                series:
                  - variant: base
                    harness: null
                    points:
                      - { date: "2026-05-08", rank: 2, elo: 1496.47, vote_count: 23846 }
                      - { date: "2026-05-09", rank: 2, elo: 1496.47, vote_count: 23846 }
                      - { date: "2026-05-10", rank: 2, elo: 1496.47, vote_count: 23846 }
                      - { date: "2026-05-11", rank: 2, elo: 1496.47, vote_count: 23846 }
        "404":
          $ref: "#/components/responses/NotFound"

  /api/rapid/v1/movers:
    get:
      tags: [Trends]
      summary: Top climbers and fallers between snapshot dates
      description: |
        Computes the rank delta between the earliest snapshot taken on or
        after `days` ago and the latest snapshot. Climber delta is positive
        (rank number went down = up the leaderboard).
      parameters:
        - $ref: "#/components/parameters/Category"
        - $ref: "#/components/parameters/Days"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MoversResponse"
              example:
                category: text
                from: "2026-05-08"
                to: "2026-06-05"
                days: 30
                limit: 3
                climbers:
                  - name: "OpenAI: GPT-5.3 Chat"
                    slug: openai/gpt-5.3-chat
                    rank_was: 115
                    rank_now: 46
                    delta: 69
                    elo: 1448.47
                  - name: "Anthropic: Claude Opus 4"
                    slug: anthropic/claude-opus-4
                    rank_was: 138
                    rank_now: 100
                    delta: 38
                    elo: 1412.21
                fallers:
                  - name: "Z.ai: GLM 4.5"
                    slug: z-ai/glm-4.5
                    rank_was: 51
                    rank_now: 102
                    delta: -51
                    elo: 1411.04
                  - name: "Qwen: Qwen3 Next 80B A3B Instruct"
                    slug: qwen/qwen3-next-80b-a3b-instruct
                    rank_was: 75
                    rank_now: 113
                    delta: -38
                    elo: 1401.74
        "400":
          $ref: "#/components/responses/BadRequest"

  /api/rapid/v1/best_for/{slug}:
    get:
      tags: [Picks]
      summary: Use-case-tailored picks
      description: |
        Pre-baked picks tuned for a use case. `coding` is the top of
        text/coding category; `multilingual` averages rank across language
        subsets; `cheap` returns the Pareto frontier by cost.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            enum: [coding, multilingual, cheap]
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BestForResponse"
              example:
                slug: coding
                title: "Best LLMs for Coding (2026)"
                description: "Top AI models for code generation, ranked by LMArena coding-category Elo and joined with OpenRouter pricing. Updated daily."
                total: 130
                limit: 2
                models:
                  - rank: 1
                    name: "Anthropic: Claude Opus 4.6"
                    slug: anthropic/claude-opus-4.6
                    arena_model_name: claude-opus-4-6-thinking
                    elo: 1552.68
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    is_or: true
                    note: null
                  - rank: 2
                    name: "Anthropic: Claude Opus 4.7"
                    slug: anthropic/claude-opus-4.7
                    arena_model_name: claude-opus-4-7-thinking
                    elo: 1551.84
                    prompt_per_m_usd: 5
                    completion_per_m_usd: 25
                    image_per_unit_usd: null
                    is_or: true
                    note: null
        "400":
          $ref: "#/components/responses/BadRequest"

components:
  parameters:
    Category:
      name: category
      in: query
      required: true
      schema:
        $ref: "#/components/schemas/Category"
    Limit:
      name: limit
      in: query
      description: Hard-capped at 500.
      schema:
        type: integer
        minimum: 1
        maximum: 500
        default: 10
    Offset:
      name: offset
      in: query
      schema:
        type: integer
        minimum: 0
        default: 0
    Days:
      name: days
      in: query
      description: Hard-capped at 365.
      schema:
        type: integer
        minimum: 1
        maximum: 365
        default: 30

  schemas:

    Category:
      type: string
      enum: [text, webdev, vision, text_to_image, image_edit, text_to_video, image_to_video, video_edit]

    LeaderboardRow:
      type: object
      properties:
        rank: { type: integer, example: 1 }
        name: { type: string, example: Claude Opus 4.6 }
        slug:
          type: string
          nullable: true
          description: OpenRouter id, or null for arena-only rows.
          example: anthropic/claude-opus-4.6
        arena_model_name: { type: string, example: claude-opus-4-5-20251101 }
        variant: { type: string, example: base }
        harness: { type: string, nullable: true, example: null }
        elo: { type: number, example: 1452.31 }
        prompt_per_m_usd: { type: number, nullable: true, example: 15 }
        completion_per_m_usd: { type: number, nullable: true, example: 75 }
        image_per_unit_usd: { type: number, nullable: true }
        is_or: { type: boolean, example: true }
        wavespeed:
          $ref: "#/components/schemas/WavespeedSummary"

    WavespeedSummary:
      type: object
      nullable: true
      description: |
        Primary Wavespeed variant matching the row's subset, or null when no
        partner-hosted variant matches.
      properties:
        model_id: { type: string }
        url: { type: string, format: uri }
        default_call_usd: { type: number, nullable: true }
        default_call_label: { type: string, nullable: true }
        unit_label: { type: string, nullable: true }

    Placement:
      type: object
      properties:
        category: { $ref: "#/components/schemas/Category" }
        rank: { type: integer }
        elo: { type: number }
        variant: { type: string }
        harness: { type: string, nullable: true }

    LeaderboardResponse:
      type: object
      properties:
        category: { $ref: "#/components/schemas/Category" }
        free: { type: boolean, nullable: true }
        total: { type: integer, example: 360 }
        limit: { type: integer, example: 10 }
        offset: { type: integer, example: 0 }
        models:
          type: array
          items: { $ref: "#/components/schemas/LeaderboardRow" }

    SearchResponse:
      type: object
      properties:
        category: { $ref: "#/components/schemas/Category" }
        filters:
          type: object
          properties:
            free: { type: boolean, nullable: true }
            min_elo: { type: number, nullable: true }
            max_prompt_per_m_usd: { type: number, nullable: true }
            max_completion_per_m_usd: { type: number, nullable: true }
            min_context_length: { type: integer, nullable: true }
            input_modality: { type: string, nullable: true }
            output_modality: { type: string, nullable: true }
        total: { type: integer }
        limit: { type: integer }
        offset: { type: integer }
        models:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/LeaderboardRow"
              - type: object
                properties:
                  context_length: { type: integer, nullable: true }
                  input_modalities:
                    type: array
                    items: { type: string }
                  output_modalities:
                    type: array
                    items: { type: string }

    PicksResponse:
      type: object
      properties:
        category: { $ref: "#/components/schemas/Category" }
        top_ranked:
          allOf:
            - $ref: "#/components/schemas/LeaderboardRow"
          nullable: true
        best_value:
          allOf:
            - $ref: "#/components/schemas/LeaderboardRow"
          nullable: true
        recommended:
          allOf:
            - $ref: "#/components/schemas/LeaderboardRow"
          nullable: true

    CompareResponse:
      type: object
      properties:
        requested:
          type: array
          items: { type: string }
        cap: { type: integer, example: 10 }
        truncated: { type: boolean }
        missing:
          type: array
          items: { type: string }
        models:
          type: array
          items:
            type: object
            properties:
              slug: { type: string }
              name: { type: string }
              description: { type: string, nullable: true }
              context_length: { type: integer, nullable: true }
              input_modalities:
                type: array
                items: { type: string }
              output_modalities:
                type: array
                items: { type: string }
              pricing:
                type: object
                additionalProperties: true
              placements:
                type: array
                items: { $ref: "#/components/schemas/Placement" }
              wavespeed_variant_count: { type: integer }

    Provider:
      type: object
      properties:
        name: { type: string }
        tag: { type: string }
        context_length: { type: integer, nullable: true }
        quantization: { type: string, nullable: true }
        status: { type: integer, nullable: true }
        uptime_last_1d: { type: number, nullable: true }
        latency_last_30m: { type: number, nullable: true }
        throughput_last_30m: { type: number, nullable: true }
        pricing:
          type: object
          additionalProperties: true

    WavespeedVariant:
      type: object
      properties:
        model_id: { type: string }
        display_name: { type: string }
        description: { type: string, nullable: true }
        type: { type: string, nullable: true }
        url: { type: string, format: uri }
        default_call_usd: { type: number, nullable: true }
        default_call_label: { type: string, nullable: true }
        unit_label: { type: string, nullable: true }

    ModelDetailResponse:
      type: object
      properties:
        slug: { type: string, example: anthropic/claude-opus-4.6 }
        name: { type: string }
        canonical_slug: { type: string, nullable: true }
        description: { type: string, nullable: true }
        context_length: { type: integer, nullable: true }
        input_modalities:
          type: array
          items: { type: string }
        output_modalities:
          type: array
          items: { type: string }
        pricing:
          type: object
          additionalProperties: true
        leaderboard:
          type: array
          items:
            type: object
            properties:
              subset: { $ref: "#/components/schemas/Category" }
              category: { type: string, example: overall }
              variant: { type: string }
              harness: { type: string, nullable: true }
              rank: { type: integer }
              elo: { type: number }
              vote_count: { type: integer, nullable: true }
        providers:
          type: array
          items: { $ref: "#/components/schemas/Provider" }
        partners:
          type: object
          properties:
            wavespeed:
              type: object
              nullable: true
              properties:
                variants:
                  type: array
                  items: { $ref: "#/components/schemas/WavespeedVariant" }

    HistorySeries:
      type: object
      properties:
        variant: { type: string }
        harness: { type: string, nullable: true }
        points:
          type: array
          items:
            type: object
            properties:
              date: { type: string, format: date, example: "2026-05-22" }
              rank: { type: integer }
              elo: { type: number }
              vote_count: { type: integer, nullable: true }

    HistoryResponse:
      type: object
      properties:
        slug: { type: string }
        category: { $ref: "#/components/schemas/Category" }
        days: { type: integer }
        series:
          type: array
          items: { $ref: "#/components/schemas/HistorySeries" }

    Mover:
      type: object
      properties:
        name: { type: string }
        slug: { type: string }
        rank_was: { type: integer }
        rank_now: { type: integer }
        delta: { type: integer, description: "Positive = climbed (rank went down)." }
        elo: { type: number }

    MoversResponse:
      type: object
      properties:
        category: { $ref: "#/components/schemas/Category" }
        from: { type: string, format: date }
        to: { type: string, format: date }
        days: { type: integer }
        limit: { type: integer }
        climbers:
          type: array
          items: { $ref: "#/components/schemas/Mover" }
        fallers:
          type: array
          items: { $ref: "#/components/schemas/Mover" }

    BestForResponse:
      type: object
      properties:
        slug: { type: string, enum: [coding, multilingual, cheap] }
        title: { type: string }
        description: { type: string }
        total: { type: integer }
        limit: { type: integer }
        models:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/LeaderboardRow"
              - type: object
                properties:
                  note: { type: string, nullable: true }

    MetaResponse:
      type: object
      properties:
        categories:
          type: array
          items: { $ref: "#/components/schemas/Category" }
        best_for_slugs:
          type: array
          items: { type: string, enum: [coding, multilingual, cheap] }
        limits:
          type: object
          properties:
            max_limit: { type: integer, example: 500 }
            max_days: { type: integer, example: 365 }
            max_compare_ids: { type: integer, example: 10 }
        endpoints:
          type: array
          items:
            type: object
            properties:
              path: { type: string }
              params:
                type: array
                items: { type: string }

    Error:
      type: object
      properties:
        error: { type: string }
        supported:
          type: array
          items: { type: string }
          description: Present on `unknown_category` / `unknown_slug` errors.

  responses:
    BadRequest:
      description: Bad Request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Not Found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
