{
  "openapi": "3.1.0",
  "info": {
    "title": "weBump Platform API preview",
    "version": "2026-09-19",
    "description": "Developer preview: reviewed projects, explicit player approval, scoped bearer tokens, game-defined private JSON saves, separately opted-in selected shared JSON, public showcases and eligible visitors. Public client IDs are not secrets: copying configuration can initiate a request but cannot alone access protected player data. Redirect callbacks protect code delivery with PKCE, not the origin of later API requests. Reviewed device grants authorize the device the player confirms; neither method attests an official app binary. CORS is not client authentication. See https://developer.webump.app/tutorial#security-model. Rate-limit budgets and retry guidance: https://developer.webump.app/rate-limits.html."
  },
  "servers": [
    {
      "url": "https://api.webump.app"
    },
    {
      "url": "https://winnie.taila760d9.ts.net:8443",
      "description": "Isolated development; sandbox projects only when platform is enabled."
    }
  ],
  "paths": {
    "/oauth/authorize": {
      "get": {
        "operationId": "get_oauth_authorize",
        "summary": "Begin five-minute phone approval request",
        "security": [],
        "responses": {
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "302": {
            "description": "Open the official HTTPS approval link. It carries only a request reference.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "200": {
            "description": "Returned instead of the redirect when the request sends Accept: application/json (browser games). Open app_url on the same iPhone, or authorization_url in a popup elsewhere, then handle the registered callback. Polling is status only.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "authorization_url",
                    "app_url",
                    "qr_url",
                    "request",
                    "expires_in"
                  ],
                  "properties": {
                    "authorization_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "app_url": {
                      "type": "string",
                      "description": "webump://connect?request=\u2026 opens the weBump app directly on the same device."
                    },
                    "request": {
                      "type": "string",
                      "description": "Public status reference; never a token credential."
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "qr_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "An image/svg+xml QR code for authorization_url. Show it when the player approves on a different device than the one running the game (desktop, console, TV)."
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "client_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "redirect_uri",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri",
              "description": "Exact registered HTTPS URL. No query, fragment or userinfo."
            }
          },
          {
            "in": "query",
            "name": "response_type",
            "required": true,
            "schema": {
              "const": "code"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          },
          {
            "in": "query",
            "name": "code_challenge",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          },
          {
            "in": "query",
            "name": "code_challenge_method",
            "required": true,
            "schema": {
              "const": "S256"
            }
          },
          {
            "in": "query",
            "name": "scope",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "display",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "page",
                "popup"
              ]
            },
            "description": "popup: for browser games that open the approval in a popup. The approval page then waits for the phone's decision and delivers the callback to that popup. Omit for native apps opening the system browser; the link may open the weBump app directly."
          }
        ]
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "post_oauth_token",
        "summary": "Redeem a callback code, poll device approval, or rotate a refresh token",
        "security": [],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tokens"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AuthorizationCode"
                  },
                  {
                    "$ref": "#/components/schemas/Refresh"
                  },
                  {
                    "$ref": "#/components/schemas/DeviceCode"
                  }
                ]
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AuthorizationCode"
                  },
                  {
                    "$ref": "#/components/schemas/Refresh"
                  },
                  {
                    "$ref": "#/components/schemas/DeviceCode"
                  }
                ]
              }
            }
          }
        },
        "description": "Supports authorization_code + S256 PKCE, rotating refresh_token, and urn:ietf:params:oauth:grant-type:device_code with client_id/device_code. Device polling returns authorization_pending, slow_down (add 5 seconds), access_denied, expired_token or scoped tokens once. Never retry uncertain successful redemption."
      }
    },
    "/oauth/revoke": {
      "post": {
        "operationId": "post_oauth_revoke",
        "summary": "Disconnect the token owner from this project; unknown tokens succeed",
        "security": [],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "client_id",
                  "token"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "get_v1_me",
        "summary": "Project-specific player ID and permitted own card",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/v1/me/permissions": {
      "get": {
        "operationId": "get_v1_me_permissions",
        "summary": "Effective scopes and player-controlled sharing choices",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "stats_sharing": {
                      "type": "boolean"
                    },
                    "shared_data_sharing": {
                      "type": "boolean"
                    },
                    "showcase_visibility": {
                      "type": "string"
                    },
                    "consent_version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/v1/me/state": {
      "get": {
        "operationId": "get_v1_me_state",
        "summary": "Read state and the shared document revision",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.state."
      }
    },
    "/v1/me/state/{key}": {
      "put": {
        "operationId": "put_v1_me_state_key",
        "summary": "Replace state conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.state.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {}
                },
                "required": [
                  "value"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "in": "path",
            "name": "key",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          },
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      },
      "delete": {
        "operationId": "delete_v1_me_state_key",
        "summary": "Delete state conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.state.",
        "parameters": [
          {
            "in": "path",
            "name": "key",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          },
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      }
    },
    "/v1/me/capsule": {
      "get": {
        "operationId": "get_v1_me_capsule",
        "summary": "Read capsule and the shared document revision",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.capsule."
      },
      "put": {
        "operationId": "put_v1_me_capsule",
        "summary": "Replace capsule conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.capsule.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {}
                },
                "required": [
                  "value"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      },
      "delete": {
        "operationId": "delete_v1_me_capsule",
        "summary": "Delete capsule conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.capsule.",
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      }
    },
    "/v1/me/showcase": {
      "get": {
        "operationId": "get_v1_me_showcase",
        "summary": "Read showcase and the shared document revision",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.showcase."
      },
      "put": {
        "operationId": "put_v1_me_showcase",
        "summary": "Replace showcase conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.showcase.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {}
                },
                "required": [
                  "value"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      },
      "delete": {
        "operationId": "delete_v1_me_showcase",
        "summary": "Delete showcase conditionally",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.showcase.",
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      }
    },
    "/v1/me/visitor-handoff": {
      "post": {
        "operationId": "post_v1_me_visitor_handoff",
        "summary": "Optional: player-chosen transfer of specific receipts (GET /v1/me/visitors lists eligible visitors automatically)",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "authorization_url": {
                          "type": "string"
                        },
                        "expires_in": {
                          "const": 300
                        }
                      },
                      "required": [
                        "authorization_url",
                        "expires_in"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "visitors": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Visitor"
                          },
                          "maxItems": 50
                        }
                      },
                      "required": [
                        "visitors"
                      ],
                      "additionalProperties": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires visitors.receive.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/HandoffBegin"
                  },
                  {
                    "$ref": "#/components/schemas/HandoffRedeem"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/v1/me/visitors/{reference}": {
      "get": {
        "operationId": "get_v1_me_visitors_reference",
        "summary": "Refresh a previously handed-off card; no arbitrary people lookup",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visitor"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires visitors.receive.",
        "parameters": [
          {
            "in": "path",
            "name": "reference",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/v1/me/game-data": {
      "delete": {
        "operationId": "delete_v1_me_game_data",
        "summary": "Delete game data, pairwise ID and all grants; keep the local game save",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/app/v1/platform-games": {
      "post": {
        "operationId": "post_app_v1_platform_games",
        "summary": "List own connected games and available recipients",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "required": [],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-preferences": {
      "post": {
        "operationId": "post_app_v1_platform_preferences",
        "summary": "Set global visitor-card sharing",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sharing": {
                    "type": "boolean"
                  },
                  "consent_version": {
                    "const": "2026-09-10"
                  }
                },
                "required": [
                  "sharing",
                  "consent_version"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-exclusion": {
      "post": {
        "operationId": "post_app_v1_platform_exclusion",
        "summary": "Exclude or allow a project to receive your visitor card",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "excluded": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "client_id",
                  "excluded"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-manage": {
      "post": {
        "operationId": "post_app_v1_platform_manage",
        "summary": "Disconnect, delete or change own game sharing",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "player_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "action": {
                    "enum": [
                      "disconnect",
                      "delete",
                      "settings"
                    ]
                  },
                  "stats_sharing": {
                    "type": "boolean"
                  },
                  "showcase_visibility": {
                    "enum": [
                      "private",
                      "friends",
                      "public"
                    ]
                  },
                  "shared_data_sharing": {
                    "type": "boolean",
                    "description": "First-party player choice only. Optional for old clients. Starts off; enabling requires the reviewed game.shared scope and matching shared_data_consent_version."
                  },
                  "shared_data_consent_version": {
                    "type": "string",
                    "description": "Use shared_data_consent_version returned by platform-request/platform-games. Currently 2026-09-14; separate from the existing visitor-card consent version."
                  }
                },
                "required": [
                  "player_id",
                  "action"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-request": {
      "post": {
        "operationId": "post_app_v1_platform_request",
        "summary": "Read immutable request details before presenting approval",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request": {
                    "type": "string"
                  },
                  "user_code": {
                    "type": "string",
                    "description": "For device grants, instead of request."
                  }
                },
                "required": [],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-decision": {
      "post": {
        "operationId": "post_app_v1_platform_decision",
        "summary": "Explicitly approve or deny the bound request",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request": {
                    "type": "string"
                  },
                  "approve": {
                    "type": "boolean"
                  },
                  "consent_version": {
                    "type": "string"
                  },
                  "stats_sharing": {
                    "type": "boolean"
                  },
                  "receipt_ids": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "shared_data_sharing": {
                    "type": "boolean",
                    "description": "First-party player choice only. Optional for old clients. Starts off; enabling requires the reviewed game.shared scope and matching shared_data_consent_version."
                  },
                  "shared_data_consent_version": {
                    "type": "string",
                    "description": "Use shared_data_consent_version returned by platform-request/platform-games. Currently 2026-09-14; separate from the existing visitor-card consent version."
                  },
                  "user_code": {
                    "type": "string",
                    "description": "For device grants, instead of request."
                  },
                  "device_user_code": {
                    "type": "string",
                    "description": "Exact code the player explicitly matched on their device."
                  },
                  "device_consent_version": {
                    "type": "string",
                    "const": "2026-09-19"
                  }
                },
                "required": [
                  "approve"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/app/v1/platform-encounter": {
      "post": {
        "operationId": "post_app_v1_platform_encounter",
        "summary": "Accept a fresh rolling ID and return local receipt references",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rolling_id": {
                    "type": "string",
                    "minLength": 24,
                    "maxLength": 96
                  }
                },
                "required": [
                  "rolling_id"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/assets/{hash}.png": {
      "get": {
        "summary": "Read an approved game icon",
        "description": "Serves metadata-stripped static PNG only for an active approved project. No authentication or tracking URLs.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reviewed PNG",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Unavailable asset"
          }
        }
      }
    },
    "/app/v1/platform-profile-games": {
      "post": {
        "operationId": "post_app_v1_platform_profile_games",
        "summary": "List publicly shared game cards on a profile (first-party app only)",
        "security": [
          {
            "InstallationID": [],
            "InstallationSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "friend_code": {
                    "type": "string",
                    "pattern": "^WB-[A-Z0-9]{4}-[A-Z0-9]{4}$"
                  }
                },
                "required": [
                  "friend_code"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "description": "Requires installation authentication. Returns only explicitly published showcase fields from active projects and player grants. Bilateral blocks, suspension and revocation remove cards. Private saves and capsule data are never returned; an unknown or unavailable profile returns an empty games array."
      }
    },
    "/v1/me/shared": {
      "get": {
        "operationId": "get_v1_me_shared",
        "summary": "Read your selected shared-data document",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedDocument"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.shared in both the approved project and player grant. Conditional mutations use the same revision as private state, capsule and showcase. Absent or expired publications return data {} and expires_at null."
      },
      "put": {
        "operationId": "put_v1_me_shared",
        "summary": "Explicitly publish selected game data",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedDocument"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.shared in both the approved project and player grant. Conditional mutations use the same revision as private state, capsule and showcase. Requires separate first-party player opt-in and publish:true. Replaces the single current publication, expiring in seven days. value must match data_definition.shared and fit 16 KiB in both wire JSON and JSONB text. The first publication requires a later eligible bump; updates during an uninterrupted, unexpired publication keep those visitors eligible. Withdrawal or an expiry gap starts a new publication. Private state is never copied automatically.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "value",
                  "publish"
                ],
                "properties": {
                  "value": {
                    "type": "object"
                  },
                  "publish": {
                    "const": true
                  }
                },
                "example": {
                  "publish": true,
                  "value": {
                    "version": 1,
                    "tiles": [
                      {
                        "x": 2,
                        "y": 3,
                        "kind": "grass"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      },
      "delete": {
        "operationId": "delete_v1_me_shared",
        "summary": "Erase only your published shared copy",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedDocument"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.shared in both the approved project and player grant. Conditional mutations use the same revision as private state, capsule and showcase. Keeps private state and the sharing preference. Old visitor references cannot retrieve a publication restarted after erasure or expiry. Updates during an uninterrupted, unexpired publication preserve eligibility.",
        "parameters": [
          {
            "in": "header",
            "name": "If-Match",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\"[0-9]+\"$"
            },
            "description": "Shared player document revision returned by GET. Wildcards and weak ETags are rejected."
          }
        ]
      }
    },
    "/v1/me/visitors/{reference}/shared": {
      "get": {
        "operationId": "get_v1_me_visitors_reference_shared",
        "summary": "Retrieve selected shared data through an authorized visitor reference",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedVisitorData"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "description": "Requires game.shared and visitors.receive. Reference must belong to this player grant, be redeemed, released and unexpired. The source needs a current same-project game.shared grant, explicit sharing and an uninterrupted, unexpired publication, all established before the bump epoch. Updating that document preserves eligibility; restarting after withdrawal or expiry does not. Current privacy, exclusion, block and suspension rules apply. Returns 410 consent_required if unavailable; remove cached copies on withdrawal or expiry. Payload never appears in ordinary visitor responses or app profile cards. Read the shared-data tutorial for the reviewed JSON schema subset and limits.",
        "parameters": [
          {
            "in": "path",
            "name": "reference",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/oauth/pending": {
      "post": {
        "operationId": "post_oauth_pending",
        "summary": "Public authorization status only",
        "security": [],
        "description": "Status only for all callers, including legacy POST and peek. Never returns codes, callback URLs, tokens or private device credentials. Does not register a waiting browser.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request": {
                    "type": "string"
                  },
                  "user_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval request status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "enum": [
                        "pending",
                        "complete"
                      ]
                    },
                    "expires_in": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "404": {
            "description": "Unknown or expired request reference."
          }
        },
        "parameters": []
      },
      "get": {
        "operationId": "get_oauth_pending",
        "summary": "Public authorization status only",
        "security": [],
        "parameters": [
          {
            "in": "query",
            "name": "request",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "user_code",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approval request status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "enum": [
                        "pending",
                        "complete"
                      ]
                    },
                    "expires_in": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "404": {
            "description": "Unknown or expired request reference."
          }
        },
        "description": "Status only for all callers, including legacy POST and peek. Never returns codes, callback URLs, tokens or private device credentials. Does not register a waiting browser."
      }
    },
    "/v1/me/visitors": {
      "get": {
        "operationId": "get_v1_me_visitors",
        "summary": "List the player's eligible visitors",
        "description": "Requires visitors.receive. Returns the people this player bumped, including verifiable recent history synced by weBump whose bump is unwrapped and reveal delay has passed and who still allow sharing, up to 50, ordered by reference. Listing marks them delivered, which unlocks /v1/me/visitors/{reference}/shared. Refresh when your game gains focus or before a session, with bounded polling and Retry-After backoff within your request budget. The visitor handoff remains available for an explicit, player-chosen transfer.",
        "security": [
          {
            "PlayerToken": []
          }
        ],
        "responses": {
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "200": {
            "description": "Success; always Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "visitors"
                  ],
                  "properties": {
                    "visitors": {
                      "type": "array",
                      "maxItems": 50,
                      "items": {
                        "$ref": "#/components/schemas/Visitor"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/oauth/qr": {
      "get": {
        "operationId": "get_oauth_qr",
        "summary": "QR code for an approval request",
        "security": [],
        "description": "Supply exactly one request reference (redirect flow) or user_code (device flow). Returns image/svg+xml encoding the public approval link for a live request. Never contains device_code or an authorization code. Expired or unknown requests return 404. Rate limited per reference.",
        "parameters": [
          {
            "in": "query",
            "name": "request",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          },
          {
            "in": "query",
            "name": "user_code",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[BCDFGHJKLMNPQRSTVWXZ2456789]{4}-[BCDFGHJKLMNPQRSTVWXZ2456789]{4}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A self-contained SVG with no script, external references or embedded links.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "404": {
            "description": "Unknown or expired request reference."
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/oauth/device_authorization": {
      "post": {
        "operationId": "device_authorization",
        "summary": "Start an explicitly reviewed public-client device connection",
        "security": [],
        "responses": {
          "200": {
            "description": "Device authorization; no-store. Keep device_code private. Display user_code plus QR/manual verification link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device_code": {
                      "type": "string"
                    },
                    "user_code": {
                      "type": "string"
                    },
                    "verification_uri": {
                      "type": "string"
                    },
                    "verification_uri_complete": {
                      "type": "string"
                    },
                    "app_url": {
                      "type": "string"
                    },
                    "qr_url": {
                      "type": "string"
                    },
                    "expires_in": {
                      "type": "integer",
                      "const": 300
                    },
                    "interval": {
                      "type": "integer",
                      "const": 5
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "410": {
            "$ref": "#/components/responses/Error410"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "428": {
            "$ref": "#/components/responses/Error428"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client_id"
                ],
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "description": "Requires device_code in project.connection_methods. No callback or PKCE parameters. Display user_code on the requesting device and require matching confirmation in weBump; device_code stays private. Device authorization does not attest genuine software or enforce callback-origin delivery."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PlayerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Opaque project/player token. Installation secrets are never accepted."
      },
      "InstallationID": {
        "type": "apiKey",
        "in": "header",
        "name": "x-webump-installation-id"
      },
      "InstallationSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-webump-installation-secret"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "Tokens": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          },
          "token_type": {
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "maximum": 600
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "refresh_token",
          "token_type",
          "expires_in",
          "scope"
        ],
        "additionalProperties": false
      },
      "Document": {
        "type": "object",
        "properties": {
          "data": {},
          "revision": {
            "type": "integer",
            "minimum": 0
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "data",
          "revision",
          "updated_at"
        ],
        "additionalProperties": false
      },
      "Visitor": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "theme_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "stats": {
            "type": "object"
          },
          "capsule": {
            "type": "object"
          },
          "stats_revision": {
            "type": "string",
            "description": "Content revision of the shared stats; private state writes do not change it."
          },
          "visitor_id": {
            "type": "string",
            "format": "uuid",
            "description": "Opaque repeat-visitor recognition scoped to project/environment and receiving player. Stable while unexpired receipts maintain continuity; resets after expiry gaps, visitor opt-out/project exclusion, blocks, explicit game disconnect/revocation/deletion. Not a lookup key or permanent/global person ID. Use reference for authorized reads; never group older cards by name/color."
          }
        },
        "required": [
          "reference",
          "display_name",
          "theme_color",
          "updated_at",
          "visitor_id"
        ],
        "additionalProperties": false
      },
      "Authorization": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Exact registered HTTPS URL. No query, fragment or userinfo."
          },
          "response_type": {
            "const": "code"
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "code_challenge": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "code_challenge_method": {
            "const": "S256"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "client_id",
          "redirect_uri",
          "response_type",
          "state",
          "code_challenge",
          "code_challenge_method",
          "scope"
        ],
        "additionalProperties": false
      },
      "AuthorizationCode": {
        "type": "object",
        "properties": {
          "grant_type": {
            "const": "authorization_code"
          },
          "client_id": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "redirect_uri",
          "code",
          "code_verifier"
        ],
        "additionalProperties": false
      },
      "Refresh": {
        "type": "object",
        "properties": {
          "grant_type": {
            "const": "refresh_token"
          },
          "client_id": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "refresh_token"
        ],
        "additionalProperties": false
      },
      "HandoffBegin": {
        "type": "object",
        "properties": {
          "action": {
            "const": "begin"
          },
          "client_id": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Exact registered HTTPS URL. No query, fragment or userinfo."
          },
          "response_type": {
            "const": "code"
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "code_challenge": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "code_challenge_method": {
            "const": "S256"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "client_id",
          "redirect_uri",
          "response_type",
          "state",
          "code_challenge",
          "code_challenge_method",
          "scope"
        ],
        "additionalProperties": false
      },
      "HandoffRedeem": {
        "type": "object",
        "properties": {
          "action": {
            "const": "redeem"
          },
          "code": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "code",
          "code_verifier",
          "redirect_uri"
        ],
        "additionalProperties": false
      },
      "SharedDocument": {
        "type": "object",
        "properties": {
          "data": {},
          "revision": {
            "type": "integer",
            "minimum": 0
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "data",
          "revision",
          "updated_at"
        ],
        "additionalProperties": false,
        "description": "Selected publication, separate from private saves. data is {} and expires_at is null when absent or expired. Shares the player revision with state/capsule/showcase."
      },
      "SharedVisitorData": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "reference",
          "data",
          "expires_at"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "type": "object",
            "description": "Only the selected data validated against this project\u2019s reviewed shared schema; at most 16 KiB."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DeviceCode": {
        "type": "object",
        "properties": {
          "grant_type": {
            "const": "urn:ietf:params:oauth:grant-type:device_code"
          },
          "client_id": {
            "type": "string"
          },
          "device_code": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "device_code"
        ],
        "additionalProperties": false
      }
    },
    "responses": {
      "Error400": {
        "description": "Invalid request, OAuth grant or schema.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error401": {
        "description": "Invalid or expired token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error403": {
        "description": "Insufficient scope, suspension or consent required.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error409": {
        "description": "revision_conflict \u2014 reread and reconcile.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error410": {
        "description": "Visitor consent/reference unavailable. Remove cached personal data.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error413": {
        "description": "quota_exceeded \u2014 body or storage bound.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error428": {
        "description": "precondition_required \u2014 strong If-Match required.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error429": {
        "description": "Rate limited by a player, project, developer, network, polling or admission budget. Pause the request queue for Retry-After seconds; retry with jitter and a bounded attempt count. See https://developer.webump.app/rate-limits.html.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            },
            "description": "Seconds before another attempt."
          }
        }
      },
      "Error503": {
        "description": "Platform disabled or temporarily unavailable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
