{
  "openapi": "3.0.3",
  "info": {
    "title": "Flexslot API",
    "version": "1.0.0",
    "description": "API documentation for Flexslot decks application"
  },
  "paths": {
    "/api/public/v1/_probe/partner": {
      "get": {
        "operationId": "_probe_partner_retrieve",
        "description": "Returns the calling partner's slug, scopes, game scopes, and rate-limit tier.",
        "summary": "Partner probe (HMAC or API key)",
        "tags": [
          "Probes"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "caller": {
                      "type": "string"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "game_scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "tier": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/_probe/public": {
      "get": {
        "operationId": "_probe_public_retrieve",
        "description": "Echo the caller. Useful for partners verifying that anonymous access works as expected.",
        "summary": "Anonymous probe",
        "tags": [
          "Probes"
        ],
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "caller": {
                      "type": "string"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/_probe/user": {
      "get": {
        "operationId": "_probe_user_retrieve",
        "description": "Returns the calling user's principal id, scopes, rate-limit tier, and a human-readable identity (`user.id` / `username`). `user.id` is the bare Firebase id and matches the `author.id` on that user's public decks; `user.username` is the handle shown on the consent screen the user approved. This doubles as the userinfo endpoint for OAuth2 Bearer callers — no extra scope is required. Email is intentionally NOT returned: it is not consistently shown to the user before consent (the screen shows username, falling back to email only when username is empty, and the skip-consent re-authorization path shows neither), so exposing it to every bearer caller would leak PII a `decks:read`-only partner never had the user approve.",
        "summary": "User PAT probe",
        "tags": [
          "Probes"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "caller",
                    "user",
                    "scopes",
                    "tier"
                  ],
                  "properties": {
                    "caller": {
                      "type": "string"
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "username"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "username": {
                          "type": "string"
                        }
                      }
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "tier": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/games/{game}/cards/resolve": {
      "post": {
        "operationId": "games_cards_resolve",
        "description": "Resolves a list of partner-supplied card identifiers to canonical Flexslot cards. Used by partners to validate references before submitting deck imports. Strict mode (``?strict=true`` query string OR ``\"strict\": true`` body flag) returns 422 if any identifiers are unresolved.",
        "summary": "Resolve card identifiers",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "strict",
            "schema": {
              "type": "boolean"
            },
            "description": "When true, return 422 FLEXSLOT_CARD_RESOLUTION_FAILED if any submitted identifier cannot be resolved."
          }
        ],
        "tags": [
          "Cards"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardResolveRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/CardResolveRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/CardResolveRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardResolveResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks": {
      "get": {
        "operationId": "games_decks_list",
        "description": "Returns the visible decks for a game. Anonymous callers see only Public decks. Authenticated callers also see their own private/unlisted decks. Archived decks are excluded.\n\nThe ``featured_cards``, ``section_counts`` and ``colors`` fields are only present when ``include=summary`` is passed; the default payload omits them to stay lean. ``expanded_cards`` is never returned on the list — fetch the detail endpoint with ``expand=cards`` for the full inventory.",
        "summary": "List public decks for a game",
        "parameters": [
          {
            "in": "query",
            "name": "authorId",
            "schema": {
              "type": "string"
            },
            "description": "Filter by deck author (firebase user id)."
          },
          {
            "in": "query",
            "name": "createdAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "createdBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "formatId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by format id."
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "hasGuide",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't have in-deck strategy notes."
          },
          {
            "in": "query",
            "name": "hasSideboardGuide",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't have an attached sideboard guide."
          },
          {
            "in": "query",
            "name": "hasVideo",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't link a YouTube video."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "type": "string",
              "enum": [
                "summary"
              ]
            },
            "description": "Set to ``summary`` to add ``featured_cards``, ``section_counts`` and ``colors`` to each list item — enough to render a rich deck card without a per-deck detail fetch. Omitted by default to keep the list lean."
          },
          {
            "in": "query",
            "name": "isLegal",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by format-legality flag."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            },
            "description": "Page size (max 100, default 20)."
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction. Defaults to ``desc``."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer"
            },
            "description": "1-indexed page number."
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Substring filter on deck name (case-insensitive)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "editedAt",
                "likes",
                "trending",
                "views"
              ]
            },
            "description": "Sort key. Defaults to ``editedAt``."
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckSummaryItem"
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "games_decks_create",
        "description": "Creates a deck owned by the authenticated user (PAT) or the partner's ``created_by`` user (partner-only HMAC). Provenance fields are optional but, when provided, the combination of ``source_provider`` + ``source_external_id`` + ``source_external_version`` must be unique per user -- duplicates return ``409 FLEXSLOT_VALIDATION_ERROR``.",
        "summary": "Create a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckCreateRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckCreateRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckCreateRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckWriteResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          },
          "409": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}": {
      "get": {
        "operationId": "games_decks_retrieve",
        "description": "Returns a deck with author, format, featured cards, and notes. Pass ``expand=cards`` to also receive the per-section card inventory under ``expanded_cards``. Anonymous callers receive 404 for non-Public decks (existence is not leaked).",
        "summary": "Get a single deck",
        "parameters": [
          {
            "in": "query",
            "name": "expand",
            "schema": {
              "type": "string",
              "enum": [
                "cards"
              ]
            },
            "description": "Optional. Pass ``cards`` to include the deck's full card inventory."
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckDetail"
                }
              }
            },
            "description": ""
          }
        }
      },
      "patch": {
        "operationId": "games_decks_update",
        "description": "Partial update. Only the deck owner may patch their own deck; non-owners receive ``404 FLEXSLOT_NOT_FOUND`` so existence is not leaked. Each successful save increments the version counter and emits a new history record.",
        "summary": "Update a deck (partial)",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicDeckUpdateRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicDeckUpdateRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicDeckUpdateRequestRequest"
              }
            }
          }
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckWriteResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      },
      "delete": {
        "operationId": "games_decks_delete",
        "description": "Deletes a deck. Owner-only -- non-owners and unknown ids alike receive ``404 FLEXSLOT_NOT_FOUND``. Returns 204 on success.",
        "summary": "Delete a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/guide": {
      "get": {
        "operationId": "games_decks_guide_retrieve",
        "description": "Returns the strategy guide attached to a deck, or ``null`` if the deck has no guide. Premium guides are truncated to a 200-char preview for non-owner callers without content access; the ``is_truncated`` flag signals the response shape.",
        "summary": "Get a deck's strategy guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Deck Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "nullable": true,
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PublicDeckGuideOutput"
                    }
                  ]
                }
              }
            },
            "description": "The guide, or ``null`` when the deck has no attached guide. Consumers MUST null-check before accessing fields."
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      },
      "put": {
        "operationId": "games_decks_guide_upsert",
        "description": "Owner-only create-or-replace. First call against a guideless deck returns 201; subsequent calls overwrite and return 200. Non-owners receive ``404 FLEXSLOT_NOT_FOUND`` so existence is not leaked. Pure-partner HMAC callers are rejected because the write contract requires per-user attribution to the deck owner.",
        "summary": "Create or replace a deck's strategy guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Deck Guides"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckGuideUpsertRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckGuideUpsertRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckGuideUpsertRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckGuideOutput"
                }
              }
            },
            "description": ""
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckGuideOutput"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      },
      "delete": {
        "operationId": "games_decks_guide_delete",
        "description": "Owner-only delete. Non-owners and missing-guide cases both return ``404 FLEXSLOT_NOT_FOUND`` so existence is not leaked. Returns 204 on success.",
        "summary": "Delete a deck's strategy guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Deck Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/like": {
      "post": {
        "operationId": "games_decks_like",
        "description": "Idempotent toggle: first call likes, second call unlikes. Requires per-user attribution (PAT or partner-on-behalf-of-user) -- pure partner HMAC callers receive ``401 FLEXSLOT_AUTHENTICATION_REQUIRED`` because there is no user to attribute the like to.",
        "summary": "Toggle the caller's like on a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckLikeResponse"
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/rollback": {
      "post": {
        "operationId": "games_decks_rollback",
        "description": "Restores the deck's field state from the historical version at the given 1-based ``version_number`` (matching the order returned by ``GET /decks/<id>/versions``). The rollback is a forward-saving event -- the historical chain GROWS by one record. Owners only; non-owners and out-of-range versions alike return ``404 FLEXSLOT_NOT_FOUND``.",
        "summary": "Roll a deck back to a historical version",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckRollbackRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckRollbackRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckRollbackRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckWriteResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/versions": {
      "get": {
        "operationId": "games_decks_versions_list",
        "description": "Returns the deck's full edit history as ``HistoricalDeck`` records, oldest first. Position 1 is the create event; the final entry is the most recent revision. Visibility rules of the underlying deck are respected — anonymous callers receive 404 for non-Public decks.",
        "summary": "List historical versions of a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckVersion"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/versions/{n}": {
      "get": {
        "operationId": "games_decks_versions_retrieve",
        "description": "Returns the nth-oldest historical record. Position 1 is the create event. Out-of-range ``n`` returns 404 NOT_FOUND. Visibility rules of the underlying deck are respected.",
        "summary": "Get a single historical version of a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "n",
            "schema": {
              "type": "integer"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckVersion"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/{id}/views": {
      "post": {
        "operationId": "games_decks_view_increment",
        "description": "Increments ``Deck.pageviews`` with a per-visitor 1-hour dedupe window. The visitor key is the caller's ``firebase_id`` if authenticated, otherwise the request IP. Returns 200 in both the increment and the dedupe-skip cases -- no information is leaked about whether the counter actually advanced.",
        "summary": "Register a pageview for a deck",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckViewIncrementResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/export/text": {
      "post": {
        "operationId": "games_decks_export_text",
        "description": "Renders a deck as an MTG Arena / Moxfield-compatible text list. Pass ``deck_id`` to export an existing Flexslot deck (visibility / IDOR rules apply) OR ``deck_payload`` to render an inline deck supplied by the partner. Inline-payload requests surface any card-resolution warnings via the ``X-Flexslot-Export-Warnings`` response header. Responses are cached for 24h keyed by ``(deck_id, deck.updated_at)`` -- inline-payload renders bypass the cache.",
        "summary": "Export a deck as plain text",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "description": "Game slug (e.g. ``magic-the-gathering``).",
            "required": true
          }
        ],
        "tags": [
          "Exports"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckExportRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckExportRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicDeckExportRequestRequest"
              }
            }
          }
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "description": "Plain-text deck list."
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          },
          "429": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/decks/my": {
      "get": {
        "operationId": "games_decks_my_retrieve",
        "description": "Returns the authenticated user's decks for a game across all visibilities (Public, Private, Unlisted), excluding archived. Requires PAT auth — partner-only callers receive 401 because there is no user to scope to.\n\nThe ``featured_cards``, ``section_counts`` and ``colors`` fields are only present when ``include=summary`` is passed — same opt-in and same shape as ``/decks``, and the enrichment runs for the caller's private decks too. ``expanded_cards`` is never returned on the list — fetch the detail endpoint with ``expand=cards`` for the full inventory.",
        "summary": "List the caller's own decks",
        "parameters": [
          {
            "in": "query",
            "name": "authorId",
            "schema": {
              "type": "string"
            },
            "description": "Filter by deck author (firebase user id)."
          },
          {
            "in": "query",
            "name": "createdAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "createdBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "formatId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by format id."
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "hasGuide",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't have in-deck strategy notes."
          },
          {
            "in": "query",
            "name": "hasSideboardGuide",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't have an attached sideboard guide."
          },
          {
            "in": "query",
            "name": "hasVideo",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to decks that do/don't link a YouTube video."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "type": "string",
              "enum": [
                "summary"
              ]
            },
            "description": "Set to ``summary`` to add ``featured_cards``, ``section_counts`` and ``colors`` to each list item — enough to render a rich deck card without a per-deck detail fetch. Omitted by default to keep the list lean."
          },
          {
            "in": "query",
            "name": "isLegal",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by format-legality flag."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            },
            "description": "Page size (max 100, default 20)."
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction. Defaults to ``desc``."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer"
            },
            "description": "1-indexed page number."
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Substring filter on deck name (case-insensitive)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "editedAt",
                "likes",
                "trending",
                "views"
              ]
            },
            "description": "Sort key. Defaults to ``editedAt``."
          }
        ],
        "tags": [
          "Decks"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeckSummaryItem"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/games/{game}/sideboard-guides": {
      "get": {
        "operationId": "games_sideboard_guides_list",
        "description": "Returns the visible sideboard guides for a game. Anonymous callers are rejected with 401 (the endpoint requires the ``sideboards:read`` scope). Authenticated callers see public guides plus their own private guides. Archived guides are excluded.\n\nThe ``featured_cards``, ``colors`` and ``matchup_count`` fields are only present when ``include=summary`` is passed; the default payload omits them to stay lean.",
        "summary": "List public sideboard guides for a game",
        "parameters": [
          {
            "in": "query",
            "name": "archetypeId",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring filter on related matchup ``opponent_archetype``."
          },
          {
            "in": "query",
            "name": "authorId",
            "schema": {
              "type": "string"
            },
            "description": "Filter by guide author (firebase user id)."
          },
          {
            "in": "query",
            "name": "createdAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "createdBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "deckId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter to guides whose m2m membership includes this deck."
          },
          {
            "in": "query",
            "name": "editedAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "format",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring filter on the guide's plain ``format`` string. Guides have no format FK, so this is a string filter named ``format`` — a deliberate divergence from the deck list's ``formatId``."
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "hasVideo",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to guides that do/don't link a YouTube video."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "type": "string",
              "enum": [
                "summary"
              ]
            },
            "description": "Set to ``summary`` to add ``featured_cards``, ``colors`` and ``matchup_count`` to each list item — enough to render a rich guide card without a per-guide detail fetch. Omitted by default to keep the list lean."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            },
            "description": "Page size (max 100, default 20)."
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction. Defaults to ``desc``."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer"
            },
            "description": "1-indexed page number."
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Substring filter on guide name (case-insensitive)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "editedAt",
                "likes",
                "views"
              ]
            },
            "description": "Sort key. Defaults to ``editedAt``."
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideSummaryItem"
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "games_sideboard_guides_create",
        "description": "Creates a sideboard guide for the authenticated user. The caller must own the deck referenced by ``deck_id``; otherwise the request is rejected with ``403 FLEXSLOT_NOT_DECK_OWNER``. Nested ``matchups`` and ``attached_deck_ids`` arrays are optional. The primary deck attachment is auto-created by the post_save signal.",
        "summary": "Create a sideboard guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideCreateRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideCreateRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideCreateRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideDetail"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/sideboard-guides/{id}": {
      "get": {
        "operationId": "games_sideboard_guides_retrieve",
        "description": "Returns a sideboard guide with nested matchups and the m2m deck membership. Non-owners receive 404 for non-Public guides (existence is not leaked).",
        "summary": "Get a single sideboard guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideDetail"
                }
              }
            },
            "description": ""
          }
        }
      },
      "patch": {
        "operationId": "games_sideboard_guides_update",
        "description": "Partial update. Only the guide owner may patch their own guide; non-owners receive ``404 FLEXSLOT_NOT_FOUND``. Omitting a field leaves it untouched.",
        "summary": "Update a sideboard guide (partial)",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicSideboardGuideUpdateRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicSideboardGuideUpdateRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PatchedPublicSideboardGuideUpdateRequestRequest"
              }
            }
          }
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideDetail"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      },
      "delete": {
        "operationId": "games_sideboard_guides_delete",
        "description": "Deletes a sideboard guide. Owner-only -- non-owners and unknown ids alike receive ``404 FLEXSLOT_NOT_FOUND``. Cascade removes matchups + attachment rows.",
        "summary": "Delete a sideboard guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/sideboard-guides/{id}/decks": {
      "post": {
        "operationId": "games_sideboard_guides_attach_deck",
        "description": "Attaches a deck (other than the primary) to the guide via the ``SideboardGuideDeckAttachment`` through-table. The caller must own both the guide and the deck. Cross-game attempts return ``400 FLEXSLOT_VALIDATION_ERROR``. Re-attaching an already-attached deck is a no-op.",
        "summary": "Attach a deck to a sideboard guide",
        "parameters": [
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideAttachDeckRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideAttachDeckRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/PublicSideboardGuideAttachDeckRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideDetail"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/sideboard-guides/{id}/decks/{deck_id}": {
      "delete": {
        "operationId": "games_sideboard_guides_detach_deck",
        "description": "Removes a ``SideboardGuideDeckAttachment`` row. Owner-only. The primary deck cannot be detached -- attempts return ``400 FLEXSLOT_VALIDATION_ERROR``.",
        "summary": "Detach a deck from a sideboard guide",
        "parameters": [
          {
            "in": "path",
            "name": "deck_id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          },
          "400": {
            "description": "No response body"
          },
          "401": {
            "description": "No response body"
          },
          "403": {
            "description": "No response body"
          },
          "404": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/games/{game}/sideboard-guides/my": {
      "get": {
        "operationId": "games_sideboard_guides_my",
        "description": "Returns the authenticated user's sideboard guides for a game across all visibilities, excluding archived. Requires PAT auth — partner-only callers receive 401 because there is no user to scope to.\n\nThe ``featured_cards``, ``colors`` and ``matchup_count`` fields are only present when ``include=summary`` is passed; the default payload omits them to stay lean.",
        "summary": "List the caller's own sideboard guides",
        "parameters": [
          {
            "in": "query",
            "name": "archetypeId",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring filter on related matchup ``opponent_archetype``."
          },
          {
            "in": "query",
            "name": "createdAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "createdBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on creation timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "deckId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter to guides whose m2m membership includes this deck."
          },
          {
            "in": "query",
            "name": "editedAfter",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 lower bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "editedBefore",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 upper bound on last-edit timestamp (inclusive)."
          },
          {
            "in": "query",
            "name": "format",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring filter on the guide's plain ``format`` string. Guides have no format FK, so this is a string filter named ``format`` — a deliberate divergence from the deck list's ``formatId``."
          },
          {
            "in": "path",
            "name": "game",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "in": "query",
            "name": "hasVideo",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter to guides that do/don't link a YouTube video."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "type": "string",
              "enum": [
                "summary"
              ]
            },
            "description": "Set to ``summary`` to add ``featured_cards``, ``colors`` and ``matchup_count`` to each list item — enough to render a rich guide card without a per-guide detail fetch. Omitted by default to keep the list lean."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            },
            "description": "Page size (max 100, default 20)."
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction. Defaults to ``desc``."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer"
            },
            "description": "1-indexed page number."
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Substring filter on guide name (case-insensitive)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "editedAt",
                "likes",
                "views"
              ]
            },
            "description": "Sort key. Defaults to ``editedAt``."
          }
        ],
        "tags": [
          "Sideboard Guides"
        ],
        "security": [
          {
            "PartnerHmac": [],
            "PartnerSlug": [],
            "PersonalAccessToken": []
          },
          {
            "PartnerHmac": [],
            "PartnerSlug": []
          },
          {
            "ApiKey": []
          },
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2DPoP": [],
            "OAuth2DPoPProof": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSideboardGuideSummaryItem"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/health": {
      "get": {
        "operationId": "health_retrieve",
        "description": "Returns 200 OK if the public API is reachable. Used by partner monitoring.",
        "summary": "Public API liveness check",
        "tags": [
          "Probes"
        ],
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/access-tokens/": {
      "get": {
        "operationId": "me_access_tokens_self",
        "description": "Returns the metadata of the Personal Access Token used to authenticate this request. Never returns the user's other tokens — this is intentionally narrower than the internal Settings UI list. Never exposes the token hash or plaintext.",
        "summary": "Read metadata for the calling PAT",
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PatOutput"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner": {
      "get": {
        "operationId": "me_partner_detail",
        "description": "Returns the Partner row owned by the user attached to the calling PAT. Never returns the signing-secret hash or the API-key hash. Use this to read scopes, allowed_origins, rate_limit_tier, etc. before deciding whether to issue a rotation or scope-change request.",
        "summary": "Read the calling user's partner row",
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSelf"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/allowed-origins": {
      "post": {
        "operationId": "me_partner_allowed_origins_add",
        "description": "Manage the partner's allowed-origins list.\n\nPOST adds an origin (idempotent — adding an existing origin is a no-op).\nDELETE with ?origin=... removes one (404 if not present, so the partner\nknows their request had no effect).\n\nOrigins gate write-path CORS — adding a new app domain shouldn't require\na Flexslot admin to do anything. This is the most common partner\nself-service action.",
        "summary": "Add an allowed origin to the partner",
        "tags": [
          "Me"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/_OriginRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/_OriginRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/_OriginRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAllowedOriginsResponse"
                }
              }
            },
            "description": ""
          }
        }
      },
      "delete": {
        "operationId": "me_partner_allowed_origins_remove",
        "description": "Manage the partner's allowed-origins list.\n\nPOST adds an origin (idempotent — adding an existing origin is a no-op).\nDELETE with ?origin=... removes one (404 if not present, so the partner\nknows their request had no effect).\n\nOrigins gate write-path CORS — adding a new app domain shouldn't require\na Flexslot admin to do anything. This is the most common partner\nself-service action.",
        "summary": "Remove an allowed origin from the partner",
        "parameters": [
          {
            "in": "query",
            "name": "origin",
            "schema": {
              "type": "string"
            },
            "description": "Origin URL to remove. Returns 404 if not present.",
            "required": true
          }
        ],
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAllowedOriginsRemoveResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/oauth2-clients": {
      "get": {
        "operationId": "me_partner_oauth2_clients_list",
        "description": "Returns the OAuth2 clients owned by the PAT user's partner, newest first, excluding revoked clients. Never returns the client secret hash.",
        "summary": "List the calling partner's OAuth2 clients",
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OAuth2ClientSelf"
                  }
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "me_partner_oauth2_clients_create",
        "description": "Creates an OAuth2 client for the PAT user's partner. Confidential clients receive a plaintext secret in the response exactly ONCE; public clients receive an empty secret (PKCE only). Subject to a per-partner client limit.",
        "summary": "Create a new OAuth2 client",
        "tags": [
          "Me"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2ClientCreateRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2ClientCreateRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2ClientCreateRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2ClientSecretResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/oauth2-clients/{client_id}": {
      "get": {
        "operationId": "me_partner_oauth2_clients_detail",
        "description": "Returns a single OAuth2 client owned by the PAT user's partner. 404 if not owned.",
        "summary": "Read one OAuth2 client",
        "parameters": [
          {
            "in": "path",
            "name": "client_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2ClientSelf"
                }
              }
            },
            "description": ""
          }
        }
      },
      "delete": {
        "operationId": "me_partner_oauth2_clients_revoke",
        "description": "Revokes the client and cascade-revokes every grant and access/refresh token issued under it. Returns 204. 404 if the client is not owned by the caller's partner.",
        "summary": "Revoke an OAuth2 client (cascade grants + tokens)",
        "parameters": [
          {
            "in": "path",
            "name": "client_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          }
        }
      }
    },
    "/api/public/v1/me/partner/oauth2-clients/{client_id}/rotate-secret": {
      "post": {
        "operationId": "me_partner_oauth2_clients_rotate_secret",
        "description": "Mints a new client secret and returns the plaintext exactly ONCE. Only valid for confidential clients — rotating a public client returns 400. 404 if the client is not owned by the caller's partner.",
        "summary": "Rotate a confidential OAuth2 client's secret",
        "parameters": [
          {
            "in": "path",
            "name": "client_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2ClientSecretResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/rotate-api-key": {
      "post": {
        "operationId": "me_partner_rotate_api_key",
        "description": "Rotates the X-API-Key credential for the calling user's partner. If the partner has no existing API key (predates the FSG-582 self-service surface), this mints the first one. Returns the new plaintext ONCE — copy it immediately.",
        "summary": "Rotate (or generate) the partner API key",
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerRotateApiKeyResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/rotate-signing-secret": {
      "post": {
        "operationId": "me_partner_rotate_signing_secret",
        "description": "Rotates the HMAC signing secret for the calling user's partner. Returns the new plaintext ONCE — copy it immediately; we don't persist plaintext anywhere. The previous secret is invalidated as soon as this request returns 200.",
        "summary": "Rotate the partner signing secret",
        "tags": [
          "Me"
        ],
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerRotateSigningSecretResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/me/partner/scope-requests": {
      "post": {
        "operationId": "me_partner_scope_request_create",
        "description": "Submit a scope change request for admin review. Adding scopes is higher-risk than CORS-origin self-service, so requests go through Flexslot admin instead of taking effect immediately. The partner is notified of approval/rejection by email.",
        "summary": "Request additional scopes or game-scopes",
        "tags": [
          "Me"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/_ScopeChangeRequestRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/_ScopeChangeRequestRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/_ScopeChangeRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "OAuth2Bearer": []
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_ScopeChangeResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/api/public/v1/oauth/introspect": {
      "post": {
        "operationId": "oauth2_introspect",
        "description": "Returns the active state and metadata of a token previously issued by this server. Only the issuing client may introspect its own tokens — all other cases return ``{\"active\": false}``.",
        "summary": "OAuth 2.0 Token Introspection (RFC 7662)",
        "tags": [
          "OAuth2"
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2IntrospectRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "RFC 7662 §2.2 introspection response."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2IntrospectError400"
                }
              }
            },
            "description": "RFC 6749 §5.2 error envelope."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2IntrospectError401"
                }
              }
            },
            "description": "RFC 6749 §5.2 invalid_client error."
          }
        }
      }
    },
    "/api/public/v1/oauth/revoke": {
      "post": {
        "operationId": "oauth2_revoke",
        "description": "Invalidates a token (access or refresh) previously issued by this server. Always returns HTTP 200 with an empty body — unknown / other-client / already-revoked tokens are silently accepted to avoid leaking existence.",
        "summary": "OAuth 2.0 Token Revocation (RFC 7009)",
        "tags": [
          "OAuth2"
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2RevokeRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Revocation accepted. Empty body."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2RevokeError400"
                }
              }
            },
            "description": "RFC 6749 §5.2 error envelope."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2RevokeError401"
                }
              }
            },
            "description": "RFC 6749 §5.2 invalid_client error."
          }
        }
      }
    },
    "/api/public/v1/oauth/token": {
      "post": {
        "operationId": "oauth2_token",
        "description": "Exchanges an authorization code for an access + refresh token pair (``grant_type=authorization_code``) or rotates an existing refresh token (``grant_type=refresh_token``). Returns the RFC 6749 §5.1 JSON shape on success or the §5.2 error shape on failure.",
        "summary": "OAuth2 token endpoint (RFC 6749 §4.1.3 + §6)",
        "tags": [
          "OAuth2"
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuth2TokenRequestRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2TokenResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2TokenError400"
                }
              }
            },
            "description": "RFC 6749 §5.2 error."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2TokenError401"
                }
              }
            },
            "description": "RFC 6749 §5.2 invalid_client error."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AllowedScopesEnum": {
        "enum": [
          "cards:read",
          "decks:read",
          "decks:write",
          "exports:read",
          "guides:read",
          "guides:write",
          "sideboards:read",
          "sideboards:write"
        ],
        "type": "string",
        "description": "* `cards:read` - cards:read\n* `decks:read` - decks:read\n* `decks:write` - decks:write\n* `exports:read` - exports:read\n* `guides:read` - guides:read\n* `guides:write` - guides:write\n* `sideboards:read` - sideboards:read\n* `sideboards:write` - sideboards:write"
      },
      "CardIdentifierInputRequest": {
        "type": "object",
        "properties": {
          "scryfall_id": {
            "type": "string"
          },
          "mtgjson_id": {
            "type": "string"
          },
          "tcgplayer_id": {
            "type": "string"
          },
          "cardId": {
            "type": "string"
          },
          "variantId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "set": {
            "type": "string"
          },
          "collector_number": {
            "type": "string"
          }
        }
      },
      "CardResolveRequestRequest": {
        "type": "object",
        "properties": {
          "identifiers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CardIdentifierInputRequest"
            }
          },
          "format": {
            "type": "string",
            "nullable": true
          },
          "strict": {
            "type": "boolean",
            "default": false
          }
        },
        "required": [
          "identifiers"
        ]
      },
      "CardResolveResponse": {
        "type": "object",
        "properties": {
          "resolved": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "unresolved": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "resolved",
          "unresolved"
        ]
      },
      "ContentTypeEnum": {
        "enum": [
          "markdown",
          "tiptap"
        ],
        "type": "string",
        "description": "* `markdown` - markdown\n* `tiptap` - tiptap"
      },
      "DecisionEnum": {
        "enum": [
          "allow",
          "deny"
        ],
        "type": "string",
        "description": "* `allow` - allow\n* `deny` - deny"
      },
      "DeckSourceProviderEnum": {
        "enum": [
          "piltover",
          "moxfield",
          "archidekt",
          "cube_cobra",
          "mtggoldfish",
          "tappedout",
          "deckstats",
          "edhrec",
          "mtg_arena",
          "mtgo",
          "forge",
          "carde_io",
          "manual"
        ],
        "type": "string",
        "description": "* `piltover` - Piltover\n* `moxfield` - Moxfield\n* `archidekt` - Archidekt\n* `cube_cobra` - Cube Cobra\n* `mtggoldfish` - MTGGoldfish\n* `tappedout` - TappedOut\n* `deckstats` - Deckstats\n* `edhrec` - EDHREC\n* `mtg_arena` - MTG Arena\n* `mtgo` - MTGO\n* `forge` - Forge\n* `carde_io` - Carde.io\n* `manual` - Manual",
        "x-enum-varnames": [
          "PILTOVER",
          "MOXFIELD",
          "ARCHIDEKT",
          "CUBE_COBRA",
          "MTGGOLDFISH",
          "TAPPEDOUT",
          "DECKSTATS",
          "EDHREC",
          "MTG_ARENA",
          "MTGO",
          "FORGE",
          "CARDE_IO",
          "MANUAL"
        ],
        "x-enum-descriptions": [
          "Piltover",
          "Moxfield",
          "Archidekt",
          "Cube Cobra",
          "MTGGoldfish",
          "TappedOut",
          "Deckstats",
          "EDHREC",
          "MTG Arena",
          "MTGO",
          "Forge",
          "Carde.io",
          "Manual"
        ]
      },
      "DifficultyEnum": {
        "enum": [
          "VERY_EASY",
          "EASY",
          "MEDIUM",
          "HARD",
          "VERY_HARD"
        ],
        "type": "string",
        "description": "* `VERY_EASY` - VERY_EASY\n* `EASY` - EASY\n* `MEDIUM` - MEDIUM\n* `HARD` - HARD\n* `VERY_HARD` - VERY_HARD"
      },
      "FeaturedCard": {
        "type": "object",
        "description": "Structured featured/special card entry — game-agnostic.\n\nCovers MTG commander/partner/companion and Riftbound legend/champion/\nbattlefield uniformly. ``slot`` is the base type (group key);\n``slotKey`` preserves the ordinal so ``battlefield_1`` and\n``battlefield_2`` stay distinguishable.",
        "properties": {
          "slot": {
            "type": "string",
            "description": "Base special-card type, e.g. 'legend', 'battlefield'."
          },
          "slotKey": {
            "type": "string",
            "description": "Raw type incl. ordinal, e.g. 'battlefield_1'."
          },
          "label": {
            "type": "string",
            "description": "Human-readable label, e.g. 'Battlefield'."
          },
          "sortOrder": {
            "type": "integer",
            "description": "Order within the slot."
          },
          "foil": {
            "type": "boolean"
          },
          "cardId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string",
            "nullable": true,
            "description": "Absolute CDN URL to the card's full-art image. null when the card has no stored image."
          },
          "type_line": {
            "type": "string",
            "nullable": true
          },
          "set": {
            "type": "string",
            "nullable": true
          },
          "collector_number": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "cardId",
          "collector_number",
          "foil",
          "imageUrl",
          "label",
          "name",
          "set",
          "slot",
          "slotKey",
          "sortOrder",
          "type_line"
        ]
      },
      "GrantTypesEnum": {
        "enum": [
          "authorization_code",
          "refresh_token"
        ],
        "type": "string",
        "description": "* `authorization_code` - authorization_code\n* `refresh_token` - refresh_token"
      },
      "OAuth2AuthorizeDecisionRequestRequest": {
        "type": "object",
        "description": "Schema for ``POST /api/public/v1/oauth/authorize``.\n\nCarries every parameter from the original ``/authorize`` GET so the\nserver can re-validate from scratch — the client cannot bypass\nvalidation by mutating params between the preview and the POST.",
        "properties": {
          "decision": {
            "$ref": "#/components/schemas/DecisionEnum"
          },
          "client_id": {
            "type": "string",
            "minLength": 1
          },
          "redirect_uri": {
            "type": "string",
            "minLength": 1
          },
          "response_type": {
            "type": "string",
            "minLength": 1
          },
          "scope": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "default": ""
          },
          "code_challenge": {
            "type": "string",
            "minLength": 1
          },
          "code_challenge_method": {
            "type": "string",
            "minLength": 1
          },
          "prompt": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "client_id",
          "code_challenge",
          "code_challenge_method",
          "decision",
          "redirect_uri",
          "response_type",
          "scope"
        ]
      },
      "OAuth2AuthorizeDecisionResponse": {
        "type": "object",
        "description": "The decision endpoint always returns a single ``redirect`` URL.\n\nAllow → ``<partner-redirect-uri>?code=...&state=...&iss=...``\nDeny → ``<partner-redirect-uri>?error=access_denied&...&state=...&iss=...``\nRecoverable validation failure → error redirect (same shape).\n\n\"Do not redirect\" failures (invalid client/redirect_uri) raise the\ncanonical 4xx envelope and never produce a body that conforms to\nthis serializer.",
        "properties": {
          "redirect": {
            "type": "string"
          }
        },
        "required": [
          "redirect"
        ]
      },
      "OAuth2AuthorizePreviewClient": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "application_name": {
            "type": "string"
          },
          "application_logo_url": {
            "type": "string"
          },
          "application_url": {
            "type": "string"
          },
          "privacy_policy_url": {
            "type": "string"
          },
          "tos_url": {
            "type": "string"
          }
        },
        "required": [
          "application_logo_url",
          "application_name",
          "application_url",
          "client_id",
          "privacy_policy_url",
          "tos_url"
        ]
      },
      "OAuth2AuthorizePreviewResponse": {
        "type": "object",
        "description": "Discriminated union of the four preview-endpoint outcomes.\n\nAll fields are optional — exactly one branch is populated per response:\n\n- ``error_redirect`` (string URL): recoverable validation failure;\n  SPA navigates to it.\n- ``auto_grant_redirect`` (string URL): skip-consent case; SPA\n  navigates immediately.\n- ``client`` + ``scopes`` + ``user`` (all three): render consent UI.\n\nThe 4xx \"do not redirect\" branch uses the canonical\n``{error, message, details}`` envelope from the exception handler\nand never reaches this serializer.",
        "properties": {
          "error_redirect": {
            "type": "string"
          },
          "auto_grant_redirect": {
            "type": "string"
          },
          "client": {
            "$ref": "#/components/schemas/OAuth2AuthorizePreviewClient"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OAuth2AuthorizePreviewScope"
            }
          },
          "user": {
            "$ref": "#/components/schemas/OAuth2AuthorizePreviewUser"
          }
        }
      },
      "OAuth2AuthorizePreviewScope": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        },
        "required": [
          "description",
          "label",
          "slug"
        ]
      },
      "OAuth2AuthorizePreviewUser": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "username"
        ]
      },
      "OAuth2ClientCreateRequestClientTypeEnum": {
        "enum": [
          "confidential",
          "public"
        ],
        "type": "string",
        "description": "* `confidential` - confidential\n* `public` - public"
      },
      "OAuth2ClientCreateRequestRequest": {
        "type": "object",
        "properties": {
          "application_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "client_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OAuth2ClientCreateRequestClientTypeEnum"
              }
            ],
            "default": "confidential"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            }
          },
          "allowed_scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AllowedScopesEnum"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GrantTypesEnum"
            },
            "default": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "application_url": {
            "type": "string",
            "default": "",
            "maxLength": 512
          },
          "application_logo_url": {
            "type": "string",
            "default": "",
            "maxLength": 512
          },
          "privacy_policy_url": {
            "type": "string",
            "default": "",
            "maxLength": 512
          },
          "tos_url": {
            "type": "string",
            "default": "",
            "maxLength": 512
          }
        },
        "required": [
          "allowed_scopes",
          "application_name",
          "redirect_uris"
        ]
      },
      "OAuth2ClientSecretResponse": {
        "type": "object",
        "properties": {
          "client": {
            "$ref": "#/components/schemas/OAuth2ClientSelf"
          },
          "plaintext_client_secret": {
            "type": "string"
          },
          "secret_rotated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "client",
          "plaintext_client_secret",
          "secret_rotated_at"
        ]
      },
      "OAuth2ClientSelf": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "client_id": {
            "type": "string",
            "readOnly": true
          },
          "client_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OAuth2ClientTypeEnum"
              }
            ],
            "readOnly": true
          },
          "application_name": {
            "type": "string",
            "readOnly": true
          },
          "application_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "application_logo_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "privacy_policy_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "tos_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 512
            },
            "readOnly": true
          },
          "allowed_scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "readOnly": true
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 32
            },
            "readOnly": true
          },
          "require_pkce": {
            "type": "boolean",
            "readOnly": true
          },
          "dpop_bound_access_tokens": {
            "type": "boolean",
            "readOnly": true
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OAuth2ClientStatusEnum"
              }
            ],
            "readOnly": true
          },
          "secret_rotated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "allowed_scopes",
          "application_logo_url",
          "application_name",
          "application_url",
          "client_id",
          "client_type",
          "created_at",
          "dpop_bound_access_tokens",
          "grant_types",
          "id",
          "last_used_at",
          "privacy_policy_url",
          "redirect_uris",
          "require_pkce",
          "secret_rotated_at",
          "status",
          "tos_url",
          "updated_at"
        ]
      },
      "OAuth2ClientStatusEnum": {
        "enum": [
          "active",
          "suspended",
          "revoked"
        ],
        "type": "string",
        "description": "* `active` - Active\n* `suspended` - Suspended\n* `revoked` - Revoked",
        "x-enum-varnames": [
          "ACTIVE",
          "SUSPENDED",
          "REVOKED"
        ],
        "x-enum-descriptions": [
          "Active",
          "Suspended",
          "Revoked"
        ]
      },
      "OAuth2ClientTypeEnum": {
        "enum": [
          "confidential",
          "public"
        ],
        "type": "string",
        "description": "* `confidential` - Confidential\n* `public` - Public",
        "x-enum-varnames": [
          "CONFIDENTIAL",
          "PUBLIC"
        ],
        "x-enum-descriptions": [
          "Confidential",
          "Public"
        ]
      },
      "OAuth2IntrospectError400": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2IntrospectError401": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2IntrospectRequestRequest": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "minLength": 1,
            "description": "The token to introspect."
          },
          "token_type_hint": {
            "type": "string",
            "minLength": 1,
            "description": "Optional advisory hint: 'access_token' or 'refresh_token'."
          },
          "client_id": {
            "type": "string",
            "minLength": 1
          },
          "client_secret": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "token"
        ]
      },
      "OAuth2RevokeError400": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2RevokeError401": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2RevokeRequestRequest": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "minLength": 1,
            "description": "The token to revoke."
          },
          "token_type_hint": {
            "type": "string",
            "minLength": 1,
            "description": "Optional advisory hint: 'access_token' or 'refresh_token'."
          },
          "client_id": {
            "type": "string",
            "minLength": 1
          },
          "client_secret": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "token"
        ]
      },
      "OAuth2TokenError400": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2TokenError401": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "error_description"
        ]
      },
      "OAuth2TokenRequestRequest": {
        "type": "object",
        "properties": {
          "grant_type": {
            "type": "string",
            "minLength": 1,
            "description": "'authorization_code' or 'refresh_token'."
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "description": "Authorization-code grant only."
          },
          "redirect_uri": {
            "type": "string",
            "minLength": 1,
            "description": "Authorization-code grant only."
          },
          "code_verifier": {
            "type": "string",
            "minLength": 1,
            "description": "Authorization-code grant only (PKCE)."
          },
          "refresh_token": {
            "type": "string",
            "minLength": 1,
            "description": "Refresh-token grant only."
          },
          "scope": {
            "type": "string",
            "minLength": 1,
            "description": "Optional refresh-token scope narrowing."
          },
          "client_id": {
            "type": "string",
            "minLength": 1,
            "description": "When not using HTTP Basic."
          },
          "client_secret": {
            "type": "string",
            "minLength": 1,
            "description": "Confidential client only."
          }
        },
        "required": [
          "grant_type"
        ]
      },
      "OAuth2TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string"
          },
          "expires_in": {
            "type": "integer"
          },
          "refresh_token": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "expires_in",
          "refresh_token",
          "scope",
          "token_type"
        ]
      },
      "PartnerAllowedOriginsRemoveResponse": {
        "type": "object",
        "properties": {
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        },
        "required": [
          "allowed_origins"
        ]
      },
      "PartnerAllowedOriginsResponse": {
        "type": "object",
        "properties": {
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Updated full list of allowed origins."
          }
        },
        "required": [
          "allowed_origins"
        ]
      },
      "PartnerRotateApiKeyResponse": {
        "type": "object",
        "properties": {
          "plaintext_api_key": {
            "type": "string",
            "description": "Shown ONCE — copy immediately. Cannot be recovered."
          },
          "api_key_prefix": {
            "type": "string",
            "description": "First 12 chars; helpful for the partner to identify the key in logs."
          }
        },
        "required": [
          "api_key_prefix",
          "plaintext_api_key"
        ]
      },
      "PartnerRotateSigningSecretResponse": {
        "type": "object",
        "properties": {
          "plaintext_signing_secret": {
            "type": "string",
            "description": "Shown ONCE — copy immediately. Cannot be recovered."
          }
        },
        "required": [
          "plaintext_signing_secret"
        ]
      },
      "PartnerSelf": {
        "type": "object",
        "description": "Self-serializer for /me/partner.\n\nReturns only what's safe to surface to the partner owner. The signing\nsecret hash and API key hash are NEVER included — they're sha256 outputs\nbut exposing them would let an attacker who exfiltrated a hash\nindependently verify guesses against the auth backend without ever\nhitting the rate limiter, so we treat them as secrets too.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "slug": {
            "type": "string",
            "readOnly": true,
            "pattern": "^[-a-zA-Z0-9_]+$"
          },
          "name": {
            "type": "string",
            "readOnly": true
          },
          "contact_email": {
            "type": "string",
            "format": "email",
            "readOnly": true
          },
          "website_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "api_key_prefix": {
            "type": "string",
            "readOnly": true
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "readOnly": true
          },
          "game_scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 32,
              "pattern": "^[-a-zA-Z0-9_]+$"
            },
            "readOnly": true
          },
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 200
            },
            "readOnly": true
          },
          "rate_limit_tier": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RateLimitTierEnum"
              }
            ],
            "readOnly": true
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OAuth2ClientStatusEnum"
              }
            ],
            "readOnly": true
          },
          "secret_rotated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "allowed_origins",
          "api_key_prefix",
          "contact_email",
          "created_at",
          "game_scopes",
          "id",
          "last_used_at",
          "name",
          "rate_limit_tier",
          "scopes",
          "secret_rotated_at",
          "slug",
          "status",
          "updated_at",
          "website_url"
        ]
      },
      "PatOutput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "token_prefix": {
            "type": "string",
            "maxLength": 12
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "created_at",
          "id",
          "name",
          "scopes",
          "token_prefix"
        ]
      },
      "PatchedPublicDeckUpdateRequestRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 50000
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "format": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Format UUID for the deck."
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "legal": {
            "type": "boolean"
          },
          "thumbnail": {
            "type": "string",
            "maxLength": 255
          },
          "is_archived": {
            "type": "boolean"
          },
          "source_provider": {
            "$ref": "#/components/schemas/DeckSourceProviderEnum"
          },
          "source_external_id": {
            "type": "string",
            "maxLength": 128
          },
          "source_external_version": {
            "type": "string",
            "maxLength": 32
          },
          "source_external_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PatchedPublicSideboardGuideUpdateRequestRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 500000
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "format": {
            "type": "string",
            "maxLength": 50
          },
          "thumbnail": {
            "type": "string",
            "nullable": true,
            "maxLength": 255
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "premium": {
            "type": "boolean"
          },
          "is_archived": {
            "type": "boolean"
          }
        }
      },
      "PublicDeckCreateRequestRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 50000
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "format": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Format UUID for the deck."
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "legal": {
            "type": "boolean"
          },
          "thumbnail": {
            "type": "string",
            "maxLength": 255
          },
          "source_provider": {
            "$ref": "#/components/schemas/DeckSourceProviderEnum"
          },
          "source_external_id": {
            "type": "string",
            "maxLength": 128
          },
          "source_external_version": {
            "type": "string",
            "maxLength": 32
          },
          "source_external_url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "name"
        ]
      },
      "PublicDeckDetail": {
        "type": "object",
        "description": "Provides ``featured_cards`` for any deck serializer.\n\nRelies on ``special_cards__card`` (and legacy commander/partner/companion\nFKs) being prefetched/selected by the caller's selector. The\n``GameSpecialCardType`` lookup is cached on serializer context so a whole\npage of decks costs one query, not one per deck.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "readOnly": true
          },
          "author": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "game": {
            "type": "string",
            "readOnly": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "format": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "archetype": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "views": {
            "type": "integer",
            "readOnly": true
          },
          "likes": {
            "type": "integer",
            "readOnly": true
          },
          "hasGuide": {
            "type": "boolean",
            "readOnly": true
          },
          "hasSideboardGuide": {
            "type": "boolean",
            "readOnly": true
          },
          "hasVideo": {
            "type": "boolean",
            "readOnly": true
          },
          "editedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "sourceProvider": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "notes": {
            "type": "string",
            "readOnly": true
          },
          "featured_cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeaturedCard"
            },
            "readOnly": true
          },
          "sourceExternalId": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "sourceExternalUrl": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          }
        },
        "required": [
          "archetype",
          "author",
          "createdAt",
          "editedAt",
          "featured_cards",
          "format",
          "game",
          "hasGuide",
          "hasSideboardGuide",
          "hasVideo",
          "id",
          "likes",
          "name",
          "notes",
          "sourceExternalId",
          "sourceExternalUrl",
          "sourceProvider",
          "views",
          "visibility"
        ]
      },
      "PublicDeckExportRequestRequest": {
        "type": "object",
        "properties": {
          "deck_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "deck_payload": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "PublicDeckGuideOutput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "deck_id": {
            "type": "string",
            "format": "uuid"
          },
          "content_type": {
            "type": "string"
          },
          "content": {},
          "is_premium": {
            "type": "boolean"
          },
          "is_truncated": {
            "type": "boolean",
            "default": false
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "content",
          "content_type",
          "created_at",
          "deck_id",
          "id",
          "is_premium",
          "updated_at"
        ]
      },
      "PublicDeckGuideUpsertRequestRequest": {
        "type": "object",
        "properties": {
          "content_type": {
            "$ref": "#/components/schemas/ContentTypeEnum"
          },
          "content": {},
          "is_premium": {
            "type": "boolean",
            "default": false
          }
        },
        "required": [
          "content",
          "content_type"
        ]
      },
      "PublicDeckLikeResponse": {
        "type": "object",
        "properties": {
          "is_liked": {
            "type": "boolean"
          },
          "total_likes": {
            "type": "integer"
          }
        },
        "required": [
          "is_liked",
          "total_likes"
        ]
      },
      "PublicDeckRollbackRequestRequest": {
        "type": "object",
        "properties": {
          "version_number": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "version_number"
        ]
      },
      "PublicDeckSummaryItem": {
        "type": "object",
        "description": "Provides ``featured_cards`` for any deck serializer.\n\nRelies on ``special_cards__card`` (and legacy commander/partner/companion\nFKs) being prefetched/selected by the caller's selector. The\n``GameSpecialCardType`` lookup is cached on serializer context so a whole\npage of decks costs one query, not one per deck.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "readOnly": true
          },
          "author": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "game": {
            "type": "string",
            "readOnly": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "format": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "archetype": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "views": {
            "type": "integer",
            "readOnly": true
          },
          "likes": {
            "type": "integer",
            "readOnly": true
          },
          "hasGuide": {
            "type": "boolean",
            "readOnly": true
          },
          "hasSideboardGuide": {
            "type": "boolean",
            "readOnly": true
          },
          "hasVideo": {
            "type": "boolean",
            "readOnly": true
          },
          "editedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "sourceProvider": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "featured_cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeaturedCard"
            },
            "readOnly": true
          },
          "section_counts": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "readOnly": true
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          }
        },
        "required": [
          "archetype",
          "author",
          "createdAt",
          "editedAt",
          "format",
          "game",
          "hasGuide",
          "hasSideboardGuide",
          "hasVideo",
          "id",
          "likes",
          "name",
          "sourceProvider",
          "views",
          "visibility"
        ]
      },
      "PublicDeckVersion": {
        "type": "object",
        "properties": {
          "version_number": {
            "type": "integer",
            "readOnly": true
          },
          "name_at_version": {
            "type": "string",
            "readOnly": true
          },
          "edited_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "history_type": {
            "type": "string",
            "readOnly": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          }
        },
        "required": [
          "edited_at",
          "history_type",
          "name_at_version",
          "version_number",
          "visibility"
        ]
      },
      "PublicDeckViewIncrementResponse": {
        "type": "object",
        "properties": {
          "pageviews": {
            "type": "integer"
          },
          "incremented": {
            "type": "boolean"
          }
        },
        "required": [
          "incremented",
          "pageviews"
        ]
      },
      "PublicDeckWriteResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "notes": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "format": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "nullable": true
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true,
            "nullable": true
          },
          "thumbnail": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "legal": {
            "type": "boolean",
            "readOnly": true
          },
          "is_archived": {
            "type": "boolean",
            "readOnly": true
          },
          "likes": {
            "type": "integer",
            "readOnly": true
          },
          "pageviews": {
            "type": "integer",
            "readOnly": true
          },
          "version": {
            "type": "integer",
            "readOnly": true
          },
          "source_provider": {
            "type": "string",
            "readOnly": true
          },
          "source_external_id": {
            "type": "string",
            "readOnly": true
          },
          "source_external_version": {
            "type": "string",
            "readOnly": true
          },
          "source_external_url": {
            "type": "string",
            "readOnly": true
          },
          "source_imported_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "created_at",
          "format",
          "id",
          "is_archived",
          "legal",
          "likes",
          "name",
          "notes",
          "pageviews",
          "source_external_id",
          "source_external_url",
          "source_external_version",
          "source_imported_at",
          "source_provider",
          "thumbnail",
          "updated_at",
          "version",
          "visibility",
          "youtube_url"
        ]
      },
      "PublicSideboardGuideAttachDeckRequestRequest": {
        "type": "object",
        "properties": {
          "deck_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "deck_id"
        ]
      },
      "PublicSideboardGuideCreateRequestRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "deck_id": {
            "type": "string",
            "format": "uuid"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 500000
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "format": {
            "type": "string",
            "maxLength": 50
          },
          "thumbnail": {
            "type": "string",
            "nullable": true,
            "maxLength": 255
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "premium": {
            "type": "boolean"
          },
          "is_archived": {
            "type": "boolean"
          },
          "matchups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicSideboardGuideMatchupRequest"
            }
          },
          "attached_deck_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "deck_id",
          "name"
        ]
      },
      "PublicSideboardGuideDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "readOnly": true
          },
          "author": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "game": {
            "type": "string",
            "readOnly": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "deck_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "format": {
            "type": "string",
            "readOnly": true
          },
          "views": {
            "type": "integer",
            "readOnly": true
          },
          "likes": {
            "type": "integer",
            "readOnly": true
          },
          "hasMatchups": {
            "type": "boolean",
            "readOnly": true
          },
          "thumbnail": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "editedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "sourceProvider": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "description": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "notes": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "youtube_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true,
            "nullable": true
          },
          "premium": {
            "type": "boolean",
            "readOnly": true
          },
          "is_archived": {
            "type": "boolean",
            "readOnly": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "matchups": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "readOnly": true
          },
          "decks": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "readOnly": true
          },
          "attached_deck_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          }
        },
        "required": [
          "attached_deck_ids",
          "author",
          "createdAt",
          "deck_id",
          "decks",
          "description",
          "editedAt",
          "format",
          "game",
          "hasMatchups",
          "id",
          "is_archived",
          "likes",
          "matchups",
          "name",
          "notes",
          "premium",
          "sourceProvider",
          "tags",
          "thumbnail",
          "views",
          "visibility",
          "youtube_url"
        ]
      },
      "PublicSideboardGuideMatchupRequest": {
        "type": "object",
        "properties": {
          "opponent_deck_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "opponent_archetype": {
            "type": "string",
            "maxLength": 100
          },
          "difficulty": {
            "$ref": "#/components/schemas/DifficultyEnum"
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 50000
          },
          "play_notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 50000
          },
          "draw_notes": {
            "type": "string",
            "nullable": true,
            "maxLength": 50000
          },
          "sort_order": {
            "type": "integer"
          },
          "thumbnail": {
            "type": "string",
            "nullable": true,
            "maxLength": 255
          },
          "is_featured": {
            "type": "boolean"
          }
        },
        "required": [
          "opponent_deck_name"
        ]
      },
      "PublicSideboardGuideSummaryItem": {
        "type": "object",
        "description": "Provides ``featured_cards`` for any deck serializer.\n\nRelies on ``special_cards__card`` (and legacy commander/partner/companion\nFKs) being prefetched/selected by the caller's selector. The\n``GameSpecialCardType`` lookup is cached on serializer context so a whole\npage of decks costs one query, not one per deck.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "readOnly": true
          },
          "author": {
            "type": "object",
            "nullable": true,
            "readOnly": true
          },
          "game": {
            "type": "string",
            "readOnly": true
          },
          "visibility": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "deck_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "format": {
            "type": "string",
            "readOnly": true
          },
          "views": {
            "type": "integer",
            "readOnly": true
          },
          "likes": {
            "type": "integer",
            "readOnly": true
          },
          "hasMatchups": {
            "type": "boolean",
            "readOnly": true
          },
          "thumbnail": {
            "type": "string",
            "readOnly": true,
            "nullable": true
          },
          "editedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "sourceProvider": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "featured_cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeaturedCard"
            },
            "readOnly": true
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "readOnly": true
          },
          "matchup_count": {
            "type": "integer",
            "readOnly": true
          }
        },
        "required": [
          "author",
          "createdAt",
          "deck_id",
          "editedAt",
          "format",
          "game",
          "hasMatchups",
          "id",
          "likes",
          "name",
          "sourceProvider",
          "thumbnail",
          "views",
          "visibility"
        ]
      },
      "RateLimitTierEnum": {
        "enum": [
          "standard",
          "premium"
        ],
        "type": "string",
        "description": "* `standard` - Standard (1000/min)\n* `premium` - Premium (20000/min)"
      },
      "_OriginRequestRequest": {
        "type": "object",
        "properties": {
          "origin": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "Full origin to add/remove (e.g. https://app.example.com)."
          }
        },
        "required": [
          "origin"
        ]
      },
      "_ScopeChangeRequestRequest": {
        "type": "object",
        "properties": {
          "requested_scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "maxItems": 20
          },
          "requested_game_scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 32,
              "pattern": "^[-a-zA-Z0-9_]+$"
            },
            "maxItems": 10
          },
          "justification": {
            "type": "string",
            "minLength": 10,
            "maxLength": 2000
          }
        },
        "required": [
          "justification"
        ]
      },
      "_ScopeChangeResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "message",
          "status"
        ]
      },
      "DeckSourceProviderEnumPretty": {
        "type": "string",
        "enum": [
          "Piltover",
          "Moxfield",
          "Archidekt",
          "Cube Cobra",
          "MTGGoldfish",
          "TappedOut",
          "Deckstats",
          "EDHREC",
          "MTG Arena",
          "MTGO",
          "Forge",
          "Carde.io",
          "Manual"
        ],
        "description": "Pretty display names for DeckSourceProviderEnum:\n* `piltover` - Piltover\n* `moxfield` - Moxfield\n* `archidekt` - Archidekt\n* `cube_cobra` - Cube Cobra\n* `mtggoldfish` - MTGGoldfish\n* `tappedout` - TappedOut\n* `deckstats` - Deckstats\n* `edhrec` - EDHREC\n* `mtg_arena` - MTG Arena\n* `mtgo` - MTGO\n* `forge` - Forge\n* `carde_io` - Carde.io\n* `manual` - Manual",
        "x-enum-varnames": [
          "PILTOVER",
          "MOXFIELD",
          "ARCHIDEKT",
          "CUBE_COBRA",
          "MTGGOLDFISH",
          "TAPPEDOUT",
          "DECKSTATS",
          "EDHREC",
          "MTG_ARENA",
          "MTGO",
          "FORGE",
          "CARDE_IO",
          "MANUAL"
        ],
        "x-enum-descriptions": [
          "Piltover",
          "Moxfield",
          "Archidekt",
          "Cube Cobra",
          "MTGGoldfish",
          "TappedOut",
          "Deckstats",
          "EDHREC",
          "MTG Arena",
          "MTGO",
          "Forge",
          "Carde.io",
          "Manual"
        ]
      },
      "OAuth2ClientStatusEnumPretty": {
        "type": "string",
        "enum": [
          "Active",
          "Suspended",
          "Revoked"
        ],
        "description": "Pretty display names for OAuth2ClientStatusEnum:\n* `active` - Active\n* `suspended` - Suspended\n* `revoked` - Revoked",
        "x-enum-varnames": [
          "ACTIVE",
          "SUSPENDED",
          "REVOKED"
        ],
        "x-enum-descriptions": [
          "Active",
          "Suspended",
          "Revoked"
        ]
      },
      "OAuth2ClientTypeEnumPretty": {
        "type": "string",
        "enum": [
          "Confidential",
          "Public"
        ],
        "description": "Pretty display names for OAuth2ClientTypeEnum:\n* `confidential` - Confidential\n* `public` - Public",
        "x-enum-varnames": [
          "CONFIDENTIAL",
          "PUBLIC"
        ],
        "x-enum-descriptions": [
          "Confidential",
          "Public"
        ]
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Partner API key (format: ak_<token>). Alternative to HMAC for simpler integrations."
      },
      "JWT": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Enter JWT token in the format: Bearer <token>"
      },
      "OAuth2Bearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "flx_at_...",
        "description": "OAuth2 access token issued via the authorization-code + PKCE flow (format: flx_at_<token>). Grants the partner user-scoped access to act on behalf of the consenting end-user."
      },
      "OAuth2DPoP": {
        "type": "http",
        "scheme": "DPoP",
        "description": "DPoP-bound OAuth2 access token (RFC 9449). Issued by the /oauth/token endpoint when the client is registered with dpop_bound_access_tokens=true. Each protected request must include a fresh DPoP proof JWT in the DPoP header. Token format: flx_at_<token>."
      },
      "OAuth2DPoPProof": {
        "type": "apiKey",
        "in": "header",
        "name": "DPoP",
        "description": "RFC 9449 DPoP proof JWT. Required on every request that presents Authorization: DPoP <access_token>. Must carry fresh iat, jti, htm matching the request method, htu matching the request URL, and ath matching SHA-256 of the access token."
      },
      "PartnerHmac": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Signature",
        "description": "HMAC-SHA256 over canonical request string. Format: v1.<unix_seconds>.<hex_hmac_sha256>."
      },
      "PartnerSlug": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Partner-Id",
        "description": "Partner identifier slug. Required alongside X-Signature for HMAC auth."
      },
      "PersonalAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "flx_pat_...",
        "description": "User-issued personal access token (format: flx_pat_<token>)."
      }
    }
  },
  "servers": [
    {
      "url": "https://api.flexslot.gg",
      "description": "Production"
    },
    {
      "url": "https://api-dev.flexslot.gg",
      "description": "Development"
    }
  ]
}
