> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cromos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# List biggest gainers and losers

> Cards ranked by price movement over a window, gainers or losers.

Rows come back ordered by `change_bp`, biggest gainer first for `direction=up` and steepest drop
first for `direction=down`, paged by cursor exactly like [List cards](/cards/list). Pass
`expansion_id` to rank inside one expansion instead of the whole catalog.

## One row per card, and the printing depends on direction

A card can move differently on different printings. The gainers list names whichever printing
rose most; the losers list, for the same card and the same window, names whichever printing fell
most, and that can be a different printing entirely. A card whose holofoil rose while its reverse
holofoil fell appears on both lists, each row naming the printing it is actually about. Read the
printing off the row rather than assume the two lists agree on which piece of cardboard they mean.

`card_variant_id` is what names it exactly: it matches an `id` in the card's `variants`, from
[Get a card](/cards/get) or `?include=variants` on [List cards](/cards/list). `printing` is the
`type` axis alone, and one card can carry several printings that share a `type`, so matching on
that string can land on the wrong one.

## What qualifies

A printing ranks only if at least three active listings sit behind its current price, and only if
it was above a minimum price at the start of the window. A price a single seller set moves when
that seller lists or sells, not when the market does, so a printing priced that thinly is absent
from both lists rather than at the top of one.

Read `listing_count` on any printing from [Get a card](/cards/get) to see how many listings its
current price is computed from. A card can therefore be absent here while still carrying prices.

## Telling a held price from a stale one

Prices are stored as a change log, so a printing that has not moved has no row on most days.
`start_cents` for a window is carried forward from the last day it was actually observed, and
`start_as_of` is that day. When `start_as_of` sits well before the window's start, `change_bp` is
measured against a carried value rather than a fresh sighting: a card untouched for two months can
still surface here, at a real percentage computed against an old price. Compare `start_as_of` to
the window to tell "held steady" from "not seen in a while."

## A different selection from `include=change`

`?include=change` on [List cards](/cards/list) and [Get a card](/cards/get) reports change data
for a card too, but for a different printing: the one the card's displayed price is read from, not
necessarily the one that moved most. A card can lead this leaderboard on one printing's percentage
while its `include=change` figure, drawn from another, reads something smaller, or even moves the
other way. See [Change](/cards/object#change) on the card object.


## OpenAPI

````yaml openapi.json GET /v1/cards/movers
openapi: 3.0.0
info:
  title: Cromos API
  version: 1.0.0
servers:
  - url: https://api.cromos.so
security:
  - bearerAuth: []
paths:
  /v1/cards/movers:
    get:
      tags:
        - Cards
      summary: List biggest gainers and losers
      description: >-
        Cards ranked by how much their market price moved over `window`. One row
        per card: the printing reported is the one that moved most in the
        requested `direction`, so a card whose holofoil rose while its reverse
        holofoil fell appears in both lists, naming a different printing in
        each. Only Near Mint prices are considered, and a printing ranks only
        above a minimum price and with at least three active listings behind its
        current price: percent change on a bulk common is noise, and a price one
        seller set alone is not a market. Rebuilt twice daily, whenever prices
        land.
      operationId: listCardMovers
      parameters:
        - schema:
            type: string
            enum:
              - 1d
              - 7d
              - 30d
            default: 7d
            description: The period to measure over.
          required: false
          description: The period to measure over.
          name: window
          in: query
        - schema:
            type: string
            enum:
              - up
              - down
            default: up
            description: '`up` for biggest gainers, `down` for biggest losers.'
          required: false
          description: '`up` for biggest gainers, `down` for biggest losers.'
          name: direction
          in: query
        - schema:
            type: string
            description: >-
              Narrow the list to one expansion. An id that matches nothing is an
              empty page, not a 404.
          required: false
          description: >-
            Narrow the list to one expansion. An id that matches nothing is an
            empty page, not a 404.
          name: expansion_id
          in: query
        - schema:
            type: integer
            description: >-
              How many rows to return. Defaults to 50 and stops at 200; a larger
              value is clamped, not rejected.
            minimum: 1
            maximum: 200
            default: 50
          required: false
          description: >-
            How many rows to return. Defaults to 50 and stops at 200; a larger
            value is clamped, not rejected.
          name: limit
          in: query
        - schema:
            type: string
            description: >-
              The `next_cursor` from the previous page. Omit it for the first
              page. A cursor this endpoint did not issue is ignored, and you get
              the first page back.
          required: false
          description: >-
            The `next_cursor` from the previous page. Omit it for the first
            page. A cursor this endpoint did not issue is ignored, and you get
            the first page back.
          name: cursor
          in: query
      responses:
        '200':
          description: Movers
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            card_id:
                              type: string
                              description: The card that moved.
                            name:
                              type: string
                              description: Card name.
                            expansion_id:
                              type: string
                              description: The expansion the card belongs to.
                            number:
                              type: string
                              nullable: true
                              description: Collector number. Null where the card has none.
                            card_variant_id:
                              type: integer
                              description: >-
                                The stable id of the printing this row is about,
                                the same id `variants[].id` carries on the card.
                                Use it to match the row to a printing: it is
                                exact, where `printing` is not.
                            printing:
                              type: string
                              description: >-
                                Which printing moved -- the best performer in
                                the requested direction. A card can appear in
                                the gainers list on one printing and the losers
                                list on another. This is the printing `type`
                                alone (`normal`, `holo`, `reverse` and so on),
                                and it is not unique on its own: a card can have
                                two printings of the same `type`, differing on
                                `subtype`, `size`, `stamps` or `foil`. Read
                                `card_variant_id` to tell which one this is.
                            start_cents:
                              type: integer
                              description: >-
                                Market price at the start of the window, in
                                cents.
                            end_cents:
                              type: integer
                              description: Market price now, in cents.
                            change_cents:
                              type: integer
                              description: '`end_cents - start_cents`.'
                            change_bp:
                              type: integer
                              description: >-
                                Change in basis points. +34.20% is `3420`.
                                Integer, never a float.
                            start_as_of:
                              type: string
                              description: >-
                                The day `start_cents` was actually observed.
                                Earlier than the window start whenever the value
                                was carried forward -- prices are stored as a
                                change log, so a card that did not move has no
                                row on most days. Use this to tell a held price
                                from a stale one.
                            observed_on:
                              type: string
                              description: The day `end_cents` was observed.
                          required:
                            - card_id
                            - name
                            - expansion_id
                            - number
                            - card_variant_id
                            - printing
                            - start_cents
                            - end_cents
                            - change_cents
                            - change_bp
                            - start_as_of
                            - observed_on
                      next_cursor:
                        type: string
                        nullable: true
                        description: >-
                          Pass this back as `cursor` for the next page. `null`
                          means this was the last.
                    required:
                      - items
                      - next_cursor
                  error:
                    type: object
                    nullable: true
                required:
                  - data
                  - error
              example:
                data:
                  items:
                    - card_id: ecard1-77
                      name: Flaaffy
                      expansion_id: ecard1
                      number: '77'
                      card_variant_id: 2779
                      printing: reverse
                      start_cents: 169
                      end_cents: 5082
                      change_cents: 4913
                      change_bp: 290710
                      start_as_of: '2026-07-31'
                      observed_on: '2026-08-17'
                    - card_id: base2-12
                      name: Vaporeon
                      expansion_id: base2
                      number: '12'
                      card_variant_id: 447
                      printing: holo
                      start_cents: 1099
                      end_cents: 10550
                      change_cents: 9451
                      change_bp: 85996
                      start_as_of: '2026-07-31'
                      observed_on: '2026-08-17'
                    - card_id: hgss1-60
                      name: Clefairy
                      expansion_id: hgss1
                      number: '60'
                      card_variant_id: 10939
                      printing: reverse
                      start_cents: 150
                      end_cents: 1299
                      change_cents: 1149
                      change_bp: 76600
                      start_as_of: '2026-07-31'
                      observed_on: '2026-08-17'
                  next_cursor: eyJicCI6NzY2MDAsImlkIjoiaGdzczEtNjAifQ
                error: null
        '304':
          description: >-
            Not modified. The `If-None-Match` you sent still matches, and there
            is no body.
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
        '400':
          description: Bad request
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limited
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          headers:
            ETag:
              description: >-
                A strong validator over the response body. Send it back as
                `If-None-Match` to get a `304 Not Modified` when nothing has
                changed.
              schema:
                type: string
            RateLimit:
              description: >-
                Remaining budget in the current window, RFC 9239 draft-7 form:
                `limit=300, remaining=284, reset=41`. `reset` is seconds until
                the window rolls.
              schema:
                type: string
            RateLimit-Policy:
              description: >-
                The policy the budget above is drawn from. `300;w=60` is 300
                requests a minute.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        data:
          type: object
          nullable: true
        error:
          $ref: '#/components/schemas/ApiError'
      required:
        - data
        - error
    ApiError:
      type: object
      properties:
        code:
          type: string
          description: A stable machine-readable token. Switch on this, not on `message`.
        message:
          type: string
          description: A human-readable sentence. Always present, never empty.
      required:
        - code
        - message
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````