{
  "openapi": "3.1.0",
  "info": {
    "title": "Binderim Data API",
    "version": "1.0.0",
    "summary": "Collectible card catalog and latest prices.",
    "description": "Read-only JSON API for collectible card data: games, sets, cards and the **latest** prices (there is no price history).\n\n## Authentication\nSend your API key in the `X-Api-Key` header or as `Authorization: Bearer <key>`. Keys in the URL are rejected with `401`. Only `GET /v1` and `GET /v1/health` work without a key.\n\n## Plans\n| Plan | Requests per UTC day | Requests per second | Cards per response | Cards per day |\n|---|---|---|---|---|\n| Free | 100 | 1 | 10 | 1,000 |\n| Pro | 1,000 | 3 | 50 | 50,000 |\n| Business | 5,000 | 10 | 100 | 500,000 |\n\nPro and Business add currency conversion and prices from more sources. Business adds Binderim sales data, graded prices (`grades`), the `ai` block and the weekly bulk download (`GET /v1/bulk`). The plan of a key is returned as `meta.plan`.\n\n## Rate limits\nEvery answer to a request with a valid key carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Over a limit you get `429` with `Retry-After`.\n\n## Errors\nErrors are `{ \"error\": { \"code\", \"message\" } }`. Use `code` in programs; `message` is for people.\n\n## Bulk download (Business)\n`GET /v1/bulk` returns time-limited links (15 minutes) to weekly files of the whole catalog: per game `sets`, `cards`, `prices` and `ai`, plus `retired_ids`, and a `manifest.json` with record counts, sizes and SHA-256 checksums. Files are gzip-compressed JSON Lines (one JSON object per line). Each line is the same object the API returns: a `sets` line is a `SetSummary`, a `cards` line a `Card`, a `prices` line the `data` of `CardPrices`, an `ai` line `{ card_id, ai }` with a `CardAi`. Cards without prices or without an AI block have no line. Amounts are in the currency of the source; exchange rates are in the manifest (`exchange_rates`, present only while they are fresh). The call does not count against the daily quota (the per-second limit still applies) and works when the quota is used up. A new release is built every Monday at about 02:00 UTC.\n\n## Variants\nA price `variant` is a lowercase slug such as `normal`, `foil` or `etched`. Four variants have fixed public names: `reverse-holo`, `poke-ball-reverse`, `master-ball-reverse`, `1st-edition`.\n\n## Attribution\nOn the Free plan, show \"Powered by Binderim\" with a link to https://binderim.com wherever you show the data (`meta.attribution`).\n\n## Terms\nUse of the API is subject to https://dev.binderim.com/terms. Commercial use needs the Business plan; reselling the raw data is not allowed.",
    "termsOfService": "https://dev.binderim.com/terms",
    "contact": {
      "name": "Binderim",
      "email": "info@binderim.com",
      "url": "https://dev.binderim.com"
    }
  },
  "servers": [
    {
      "url": "https://dev.binderim.com",
      "description": "Production"
    }
  ],
  "x-plans": {
    "free": {
      "daily_requests": 100,
      "requests_per_second": 1,
      "cards_per_response": 10,
      "cards_per_day": 1000,
      "currency_conversion": false,
      "external_sources": false,
      "binderim_sources": false,
      "grades": false,
      "ai": false,
      "bulk_download": false,
      "attribution_required": true
    },
    "pro": {
      "daily_requests": 1000,
      "requests_per_second": 3,
      "cards_per_response": 50,
      "cards_per_day": 50000,
      "currency_conversion": true,
      "external_sources": true,
      "binderim_sources": false,
      "grades": false,
      "ai": false,
      "bulk_download": false,
      "attribution_required": false
    },
    "business": {
      "daily_requests": 5000,
      "requests_per_second": 10,
      "cards_per_response": 100,
      "cards_per_day": 500000,
      "currency_conversion": true,
      "external_sources": true,
      "binderim_sources": true,
      "grades": true,
      "ai": true,
      "bulk_download": true,
      "attribution_required": false
    }
  },
  "x-public-variant-names": [
    "reverse-holo",
    "poke-ball-reverse",
    "master-ball-reverse",
    "1st-edition"
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Service",
      "description": "Name, version and health. No key needed."
    },
    {
      "name": "Catalog",
      "description": "Games, sets and cards."
    },
    {
      "name": "Prices",
      "description": "Latest prices of cards."
    },
    {
      "name": "Bulk",
      "description": "Weekly bulk files of the whole catalog (Business)."
    }
  ],
  "paths": {
    "/v1": {
      "get": {
        "tags": [
          "Service"
        ],
        "operationId": "getRoot",
        "summary": "API name and version",
        "security": [],
        "responses": {
          "200": {
            "description": "The API name and version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Root"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Service"
        ],
        "operationId": "getHealth",
        "summary": "Health of the service",
        "security": [],
        "description": "No key and no quota. Answers 200 when the service is ready and 503 while it starts up; both answers have the same body shape.",
        "responses": {
          "200": {
            "description": "The service is ready.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "503": {
            "description": "The service is not ready yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/v1/games": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listGames",
        "summary": "List the games",
        "responses": {
          "200": {
            "description": "The games in the catalog.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamesEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/games/{game}/sets": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listSets",
        "summary": "List the sets of a game",
        "description": "All sets of the game, not paged.",
        "parameters": [
          {
            "$ref": "#/components/parameters/game"
          }
        ],
        "responses": {
          "200": {
            "description": "The sets of the game.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetsEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No game with that id.",
            "x-error-codes": [
              "game_not_found"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sets/{id}/cards": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listSetCards",
        "summary": "List the cards of a set",
        "description": "Cards in card-number order. `page_size` defaults to, and cannot exceed, the cards per response of your plan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/setId"
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/pageSize"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of cards.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetCardsEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "A query parameter is not valid.",
            "x-error-codes": [
              "invalid_parameter",
              "page_size_too_large"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No set with that id.",
            "x-error-codes": [
              "set_not_found"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cards/{publicId}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getCard",
        "summary": "Get one card",
        "description": "A retired (merged) id still works: you get the card it was merged into and `merged_from` holds the id you asked for. On the Business plan the card also has `grades` and `ai`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/publicId"
          },
          {
            "$ref": "#/components/parameters/currency"
          }
        ],
        "responses": {
          "200": {
            "description": "The card.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "The card id or a parameter is not valid.",
            "x-error-codes": [
              "invalid_id",
              "unknown_currency"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your plan does not include this.",
            "x-error-codes": [
              "plan_upgrade_required"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No card with that id.",
            "x-error-codes": [
              "card_not_found"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "410": {
            "description": "The card was removed from the catalog.",
            "x-error-codes": [
              "card_deleted"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cards/{publicId}/prices": {
      "get": {
        "tags": [
          "Prices"
        ],
        "operationId": "getCardPrices",
        "summary": "Latest prices of one card",
        "description": "One entry per source, price type and variant. `grades` is returned on the Business plan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/publicId"
          },
          {
            "$ref": "#/components/parameters/currency"
          },
          {
            "$ref": "#/components/parameters/source"
          }
        ],
        "responses": {
          "200": {
            "description": "The latest prices.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardPricesEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "The card id or a parameter is not valid.",
            "x-error-codes": [
              "invalid_id",
              "unknown_currency",
              "unknown_source"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your plan does not include this currency conversion or price source.",
            "x-error-codes": [
              "plan_upgrade_required"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No card with that id.",
            "x-error-codes": [
              "card_not_found"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "410": {
            "description": "The card was removed from the catalog.",
            "x-error-codes": [
              "card_deleted"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/prices/batch": {
      "post": {
        "tags": [
          "Prices"
        ],
        "operationId": "batchPrices",
        "summary": "Latest prices of many cards",
        "description": "The body is at most 64 KB and has at most as many distinct ids as your plan allows cards per response. Repeated ids are answered once. Every id gets its own result; ids that do not exist do not fail the request.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per distinct id.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-Success"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "The body or a parameter is not valid.",
            "x-error-codes": [
              "invalid_body",
              "batch_too_large",
              "unknown_currency",
              "unknown_source"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your plan does not include this currency conversion or price source.",
            "x-error-codes": [
              "plan_upgrade_required"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than 64 KB.",
            "x-error-codes": [
              "payload_too_large"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk": {
      "get": {
        "tags": [
          "Bulk"
        ],
        "operationId": "getBulk",
        "summary": "Weekly bulk download (Business)",
        "description": "Links to the weekly files of the whole catalog, for the first load and a full copy; use the other endpoints for daily changes. Each link is valid for 15 minutes and may be handed out again for a few minutes, so every link in an answer has at least 5 minutes left; the answer is never cached (`Cache-Control: no-store`). This call does not count against the daily quota, the per-second limit still applies, and it works when the daily quota is used up. `HEAD` is answered too. Files are gzip-compressed JSON Lines (UTF-8). They are sent as is (no `Content-Encoding`), so verify the SHA-256 checksum of the file you saved.",
        "parameters": [
          {
            "$ref": "#/components/parameters/bulkGame"
          }
        ],
        "responses": {
          "200": {
            "description": "The files of the latest completed release, each with a download link.",
            "headers": {
              "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"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked, or it was sent in the URL.",
            "x-error-codes": [
              "invalid_api_key"
            ],
            "headers": {
              "WWW-Authenticate": {
                "description": "Always `Bearer realm=\"Binderim Data API\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your plan does not include the bulk download (Business only).",
            "x-error-codes": [
              "plan_upgrade_required"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No game with that id.",
            "x-error-codes": [
              "game_not_found"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "A limit was exceeded. Wait for the number of seconds in Retry-After. On the Business plan only the per-second limit (`rate_limited`) can apply here; the daily quota never limits this endpoint. Other plans get `403` while they have quota left.",
            "x-error-codes": [
              "rate_limited",
              "daily_quota_exceeded"
            ],
            "headers": {
              "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"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "x-error-codes": [
              "internal_error"
            ],
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "The weekly files are not ready (the first release is still being generated, or storage is unavailable), or the service is starting up or busy. Retry after the number of seconds in Retry-After. These answers do not use your quota.",
            "x-error-codes": [
              "bulk_not_ready",
              "warming_up",
              "server_busy"
            ],
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Your API key."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key as a bearer token."
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Your daily quota in requests.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current UTC day.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "When the quota resets, in Unix time (seconds).",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before trying again.",
        "schema": {
          "type": "integer"
        }
      },
      "Cache-Control-Success": {
        "description": "Successful answers can be cached for 60 seconds: `private, max-age=60`.",
        "schema": {
          "type": "string"
        }
      },
      "Cache-Control-NoStore": {
        "description": "Always `no-store`: the links in the answer are credentials and must not be cached.",
        "schema": {
          "type": "string"
        }
      }
    },
    "parameters": {
      "game": {
        "name": "game",
        "in": "path",
        "required": true,
        "description": "Game id, for example `pokemon`. See `GET /v1/games`.",
        "schema": {
          "type": "string",
          "enum": [
            "pokemon",
            "one-piece",
            "riftbound",
            "mtg",
            "yugioh",
            "lorcana",
            "digimon"
          ]
        }
      },
      "setId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Set id (a number, as text).",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "publicId": {
        "name": "publicId",
        "in": "path",
        "required": true,
        "description": "Card id, for example `pkm-sv08-123-en`. Case does not matter.",
        "schema": {
          "type": "string",
          "maxLength": 160
        }
      },
      "page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Page number, from 1.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "pageSize": {
        "name": "page_size",
        "in": "query",
        "required": false,
        "description": "Cards per page. Defaults to, and cannot exceed, the cards per response of your plan (10, 50 or 100).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "currency": {
        "name": "currency",
        "in": "query",
        "required": false,
        "description": "Add converted amounts in this currency (Pro and Business).",
        "schema": {
          "type": "string",
          "enum": [
            "USD",
            "EUR",
            "GBP",
            "TRY"
          ]
        }
      },
      "source": {
        "name": "source",
        "in": "query",
        "required": false,
        "description": "Only this price source. `general` is allowed on every plan, other sources need Pro, `binderim-tr` and `binderim-uk` need Business.",
        "schema": {
          "type": "string",
          "enum": [
            "general",
            "tcgplayer",
            "cardmarket",
            "ebay",
            "ygoprodeck",
            "lorcast",
            "optcgapi",
            "binderim-tr",
            "binderim-uk"
          ]
        }
      },
      "bulkGame": {
        "name": "game",
        "in": "query",
        "required": false,
        "description": "Only the files of this game (plus the game-independent `retired_ids`). Without it, every file of the release is listed.",
        "schema": {
          "type": "string",
          "enum": [
            "pokemon",
            "one-piece",
            "riftbound",
            "mtg",
            "yugioh",
            "lorcana",
            "digimon"
          ]
        }
      }
    },
    "schemas": {
      "Root": {
        "type": "object",
        "required": [
          "name",
          "version"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "const": "Binderim Data API"
          },
          "version": {
            "type": "string",
            "const": "v1"
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "ready",
          "ai_ready"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded",
              "warming_up"
            ],
            "description": "`ok`, `degraded` (data is older than expected) or `warming_up`."
          },
          "ready": {
            "type": "boolean"
          },
          "ai_ready": {
            "type": "boolean",
            "description": "Whether the AI data (Business `ai` block) is loaded."
          },
          "catalog_built_utc": {
            "type": "string",
            "format": "date-time"
          },
          "prices_built_utc": {
            "type": "string",
            "format": "date-time"
          },
          "ai_built_utc": {
            "type": "string",
            "format": "date-time"
          },
          "bulk_generated_utc": {
            "type": "string",
            "format": "date-time",
            "description": "When the latest completed weekly bulk release was generated. Left out until the first release exists. It does not change `status`."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "invalid_api_key",
              "rate_limited",
              "daily_quota_exceeded",
              "server_busy",
              "warming_up",
              "internal_error",
              "payload_too_large",
              "not_found",
              "method_not_allowed",
              "game_not_found",
              "set_not_found",
              "card_not_found",
              "card_deleted",
              "invalid_id",
              "invalid_parameter",
              "page_size_too_large",
              "batch_too_large",
              "invalid_body",
              "unknown_source",
              "unknown_currency",
              "plan_upgrade_required",
              "bulk_not_ready"
            ],
            "description": "Stable code to use in programs."
          },
          "message": {
            "type": "string",
            "description": "English text for people. May change."
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "Meta": {
        "type": "object",
        "required": [
          "plan",
          "catalog_as_of",
          "prices_as_of"
        ],
        "additionalProperties": false,
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "pro",
              "business"
            ]
          },
          "catalog_as_of": {
            "type": "string",
            "format": "date-time",
            "description": "When the catalog snapshot was built (every 3 hours)."
          },
          "prices_as_of": {
            "type": "string",
            "format": "date-time",
            "description": "When the price snapshot was built (every hour)."
          },
          "attribution": {
            "type": "string",
            "description": "Free plan only: the text to show with a link to https://binderim.com."
          },
          "page": {
            "type": "integer"
          },
          "page_size": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "Game": {
        "type": "object",
        "required": [
          "id",
          "name",
          "set_count",
          "card_count"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "set_count": {
            "type": "integer"
          },
          "card_count": {
            "type": "integer"
          }
        }
      },
      "SetSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "card_count"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "series": {
            "type": "string"
          },
          "release_date": {
            "type": "string",
            "format": "date"
          },
          "card_count": {
            "type": "integer"
          },
          "logo": {
            "type": "string",
            "format": "uri"
          },
          "symbol": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SetRef": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "CardImages": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "large": {
            "type": "string",
            "format": "uri"
          },
          "small": {
            "type": "string",
            "format": "uri"
          },
          "back": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "CardExternalIds": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tcgplayer": {
            "type": "string"
          },
          "tcgdex": {
            "type": "string"
          },
          "pricecharting": {
            "type": "object",
            "description": "Product number of the price source per variant. Keys are variant names.",
            "propertyNames": {
              "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
            },
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Converted": {
        "type": "object",
        "required": [
          "amount",
          "currency",
          "rate",
          "rate_date"
        ],
        "additionalProperties": false,
        "properties": {
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "EUR",
              "GBP",
              "TRY"
            ]
          },
          "rate": {
            "type": "number"
          },
          "rate_date": {
            "type": "string",
            "format": "date",
            "description": "Date of the exchange rate."
          }
        }
      },
      "PriceEntry": {
        "type": "object",
        "required": [
          "source",
          "type",
          "variant",
          "amount",
          "currency",
          "as_of"
        ],
        "additionalProperties": false,
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "general",
              "pricecharting",
              "tcgplayer",
              "cardmarket",
              "ebay",
              "ygoprodeck",
              "lorcast",
              "optcgapi",
              "binderim-tr",
              "binderim-uk"
            ],
            "description": "`pricecharting` only appears inside `grades`."
          },
          "type": {
            "type": "string",
            "enum": [
              "market",
              "trend",
              "card_low",
              "sold_median",
              "last_sold"
            ],
            "description": "`card_low` is the lowest price across all printings of the card. `sold_median` and `last_sold` are Binderim sales."
          },
          "variant": {
            "type": "string",
            "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
            "description": "Lowercase slug, for example `normal`, `foil`, `reverse-holo`, `1st-edition`."
          },
          "condition": {
            "type": "string",
            "description": "Card condition of a Binderim sale."
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "EUR",
              "GBP",
              "TRY"
            ]
          },
          "as_of": {
            "type": "string",
            "format": "date-time"
          },
          "sample_size": {
            "type": "integer",
            "description": "Sales behind a `sold_median` (last 90 days)."
          },
          "converted": {
            "$ref": "#/components/schemas/Converted"
          },
          "grade": {
            "type": "string",
            "enum": [
              "psa10",
              "bgs10",
              "cgc10",
              "sgc10",
              "grade7",
              "grade8",
              "grade9",
              "grade95"
            ],
            "description": "Only in `grades`. `grade9` means grade 9 from any grading company."
          }
        }
      },
      "AiBeta": {
        "type": "object",
        "additionalProperties": false,
        "description": "Experimental; may change.",
        "properties": {
          "objects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "characters": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "costumes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CardAi": {
        "type": "object",
        "required": [
          "model_version"
        ],
        "additionalProperties": false,
        "description": "Business plan only. Values the analysis could not decide are left out.",
        "properties": {
          "model_version": {
            "type": "string"
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "emotions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "mood": {
            "type": "string"
          },
          "style": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "setting": {
            "type": "string"
          },
          "time_of_day": {
            "type": "string"
          },
          "weather": {
            "type": "string"
          },
          "character_count": {
            "type": "integer"
          },
          "description": {
            "type": "string"
          },
          "beta": {
            "$ref": "#/components/schemas/AiBeta"
          }
        }
      },
      "Card": {
        "type": "object",
        "required": [
          "id",
          "game",
          "set",
          "language"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
            "maxLength": 160
          },
          "game": {
            "type": "string"
          },
          "set": {
            "$ref": "#/components/schemas/SetRef"
          },
          "name": {
            "type": "string"
          },
          "number": {
            "type": "string"
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "ja",
              "zh",
              "ko"
            ]
          },
          "rarity": {
            "type": "string"
          },
          "images": {
            "$ref": "#/components/schemas/CardImages"
          },
          "external_ids": {
            "$ref": "#/components/schemas/CardExternalIds"
          },
          "merged_from": {
            "type": "string",
            "description": "Only when you asked with a retired id: the id you asked for."
          },
          "grades": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceEntry"
            },
            "description": "Business plan only."
          },
          "ai": {
            "$ref": "#/components/schemas/CardAi"
          }
        }
      },
      "CardPrices": {
        "type": "object",
        "required": [
          "card_id",
          "prices"
        ],
        "additionalProperties": false,
        "properties": {
          "card_id": {
            "type": "string"
          },
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceEntry"
            }
          },
          "grades": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceEntry"
            },
            "description": "Business plan only."
          },
          "merged_from": {
            "type": "string"
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "ids"
        ],
        "additionalProperties": false,
        "properties": {
          "ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "Card ids. At most your plan's cards per response distinct ids."
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "EUR",
              "GBP",
              "TRY"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "general",
              "tcgplayer",
              "cardmarket",
              "ebay",
              "ygoprodeck",
              "lorcast",
              "optcgapi",
              "binderim-tr",
              "binderim-uk"
            ]
          }
        }
      },
      "BatchItem": {
        "type": "object",
        "required": [
          "requested_id",
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "requested_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "merged",
              "deleted",
              "not_found",
              "invalid_id"
            ]
          },
          "id": {
            "type": "string",
            "description": "Status `ok`: the card id."
          },
          "merged_into": {
            "type": "string",
            "description": "Status `merged`: the id to use from now on. The prices are those of that card."
          },
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceEntry"
            }
          },
          "grades": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceEntry"
            },
            "description": "Business plan only."
          }
        }
      },
      "GamesEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "SetsEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SetSummary"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "SetCardsEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Card"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "CardEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Card"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "CardPricesEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CardPrices"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "BatchEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchItem"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "BulkFile": {
        "type": "object",
        "required": [
          "kind",
          "records",
          "bytes",
          "sha256",
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "game": {
            "type": "string",
            "enum": [
              "pokemon",
              "one-piece",
              "riftbound",
              "mtg",
              "yugioh",
              "lorcana",
              "digimon"
            ],
            "description": "Left out for `retired_ids`, which covers all games."
          },
          "kind": {
            "type": "string",
            "enum": [
              "sets",
              "cards",
              "prices",
              "ai",
              "retired_ids"
            ],
            "description": "What the file holds. Lines of `sets` are `SetSummary`, of `cards` are `Card` (without `grades` and `ai`), of `prices` are the `data` of `CardPrices` (cards without prices have no line), of `ai` are `{ \"card_id\", \"ai\" }` with a `CardAi` (cards without an AI block have no line), of `retired_ids` are `{ \"id\", \"status\", \"merged_into\" }` with the same `status` values as the batch endpoint (`merged` or `deleted`)."
          },
          "records": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of lines in the file."
          },
          "bytes": {
            "type": "integer",
            "minimum": 0,
            "description": "Size of the compressed file."
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "SHA-256 of the compressed file, in lowercase hexadecimal."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Download link, valid until `url_expires_at`. The file is sent as is, without `Content-Encoding`."
          }
        }
      },
      "BulkRelease": {
        "type": "object",
        "required": [
          "release",
          "generated_at",
          "catalog_as_of",
          "prices_as_of",
          "next_release_after",
          "url_expires_at",
          "manifest_url",
          "files"
        ],
        "additionalProperties": false,
        "properties": {
          "release": {
            "type": "string",
            "pattern": "^[0-9]{8}T[0-9]{6}Z$",
            "description": "Release id: when the generation started (UTC), for example `20261026T020012Z`."
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "catalog_as_of": {
            "type": "string",
            "format": "date-time",
            "description": "Age of the catalog data in the files."
          },
          "prices_as_of": {
            "type": "string",
            "format": "date-time",
            "description": "Age of the price data in the files."
          },
          "ai_as_of": {
            "type": "string",
            "format": "date-time",
            "description": "Age of the AI data in the files."
          },
          "next_release_after": {
            "type": "string",
            "format": "date-time",
            "description": "The next release is generated after this time (every Monday at 02:00 UTC)."
          },
          "url_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the earliest link in this answer stops working (every link has at least 5 minutes left when the answer is made). Ask again for new links; a download that has started is not cut."
          },
          "manifest_url": {
            "type": "string",
            "format": "uri",
            "description": "Link to `manifest.json`: sizes, record counts, SHA-256 checksums and, while fresh, the exchange rates."
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkFile"
            }
          }
        }
      },
      "BulkEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "properties": {
          "data": {
            "$ref": "#/components/schemas/BulkRelease"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "Unknown endpoint. This answer comes from any URL under /v1 that is not an endpoint.",
        "x-error-codes": [
          "not_found"
        ],
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The endpoint exists but not for this HTTP method. The Allow header lists the methods that work.",
        "x-error-codes": [
          "method_not_allowed"
        ],
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  }
}
