{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "Provibet API",
    "version": "1.0.0-preview",
    "summary": "Contrato previsto para integración prepaga de juegos.",
    "description": "Vista previa técnica. No acredita disponibilidad de entorno, integración habilitada ni autorización de producción. URL prevista sujeta a habilitación por administración. Cuenta y credenciales se gestionan de forma administrada. Fichas CRD como strings decimales de hasta dos posiciones; respuestas monetarias con dos posiciones. Incluye descuentos de fichas del jugador hacia la billetera prepaga (debits). No se definen pagos en dinero ni callbacks de apuestas o premios. La cadena canónica HMAC de nueve líneas se documenta en https://provibet.com/docs/#/autenticacion. Nunca enviar el secreto al navegador ni en peticiones. Las respuestas admiten campos adicionales para evolución compatible."
  },
  "servers": [
    {
      "url": "https://api.provibet.com/api/b2b/v1",
      "description": "URL prevista. Usar sólo después de la habilitación expresa del entorno."
    }
  ],
  "externalDocs": {
    "url": "https://provibet.com/docs/",
    "description": "Documentación de integración de Provibet"
  },
  "tags": [
    {
      "name": "Billetera"
    },
    {
      "name": "Jugadores"
    },
    {
      "name": "Fichas",
      "description": "Entregas (credits) y descuentos (debits) de fichas de jugadores, con idempotencia y conciliación por operación."
    },
    {
      "name": "Juegos"
    },
    {
      "name": "Operaciones"
    },
    {
      "name": "Navegador",
      "description": "URL temporal devuelta por la apertura. No construir tickets."
    },
    {
      "name": "Sitio",
      "description": "Diseño publicado del sitio del revendedor: banners, menús, listas curadas y productos."
    }
  ],
  "security": [
    {
      "B2BPublicKey": [],
      "B2BSignature": []
    }
  ],
  "paths": {
    "/wallet": {
      "get": {
        "operationId": "getWallet",
        "summary": "Consultar billetera prepaga",
        "tags": [
          "Billetera"
        ],
        "x-required-scope": "wallet:read",
        "description": "Scope requerido: wallet:read. Toda llamada de backend requiere los cuatro encabezados HMAC. ",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saldo disponible y reservado.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletResponse"
                },
                "example": {
                  "wallet": {
                    "currency": "CRD",
                    "availableCredits": "9500.00",
                    "reservedCredits": "500.00",
                    "version": "14"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/players": {
      "post": {
        "operationId": "createPlayer",
        "summary": "Crear o recuperar jugador",
        "tags": [
          "Jugadores"
        ],
        "x-required-scope": "players:write",
        "description": "Scope requerido: players:write. Toda llamada de backend requiere los cuatro encabezados HMAC. externalPlayerId es estable en tu integración. No se entregan credenciales del jugador.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatorio en todo POST (8 a 128 caracteres) y parte de la firma. En este endpoint no deduplica: usá un valor nuevo por solicitud.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$",
              "minLength": 8,
              "maxLength": 128,
              "examples": [
                "player-jugador-123"
              ]
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Jugador nuevo.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerResponse"
                },
                "example": {
                  "player": {
                    "externalPlayerId": "jugador-123"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlayerRequest"
              },
              "example": {
                "externalPlayerId": "jugador-123",
                "displayName": "Jugador 123"
              }
            }
          }
        }
      }
    },
    "/players/{externalPlayerId}/balance": {
      "get": {
        "operationId": "getPlayerBalance",
        "summary": "Consultar saldo del jugador",
        "tags": [
          "Jugadores"
        ],
        "x-required-scope": "players:read",
        "description": "Scope requerido: players:read. Toda llamada de backend requiere los cuatro encabezados HMAC. ",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "externalPlayerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]{2,127}$",
              "minLength": 3,
              "maxLength": 128,
              "examples": [
                "jugador-123"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saldo del jugador.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                },
                "example": {
                  "player": {
                    "externalPlayerId": "jugador-123",
                    "balanceCredits": "250.00",
                    "currency": "CRD"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/players/{externalPlayerId}/credits": {
      "post": {
        "operationId": "creditPlayer",
        "summary": "Entregar fichas prepagadas",
        "tags": [
          "Fichas"
        ],
        "x-required-scope": "credits:write",
        "description": "Scope requerido: credits:write. Toda llamada de backend requiere los cuatro encabezados HMAC. Una intención usa una clave y bytes exactos persistidos. Un timeout no confirma rechazo. Reintentar con timestamp/nonce/firma nuevos, misma clave, método, ruta, query y body. La API reserva fichas antes de procesar y conserva la reserva cuando el resultado es incierto.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "externalPlayerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]{2,127}$",
              "minLength": 3,
              "maxLength": 128,
              "examples": [
                "jugador-123"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Identidad de la intención. Persistir antes de enviar. En una entrega, reutilizar la misma clave, método, path, query y body en reintentos; renovar nonce y firma.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$",
              "minLength": 8,
              "maxLength": 128,
              "examples": [
                "credit-order-5001"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entrega COMPLETED. replayed indica repetición de la intención.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "kind": "CREDIT",
                    "state": "COMPLETED",
                    "amountCredits": "50.00",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "202": {
            "description": "RESERVED o UNKNOWN. Conservar reserva y consultar UUID. No iniciar otra intención.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "kind": "CREDIT",
                    "state": "UNKNOWN",
                    "amountCredits": "50.00",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "description": "Entrega REJECTED; reserva liberada. Esta respuesta contiene operation, no el formato general de error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "kind": "CREDIT",
                    "state": "REJECTED",
                    "amountCredits": "50.00",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditRequest"
              },
              "example": {
                "amountCredits": "50",
                "currency": "CRD"
              }
            }
          }
        }
      }
    },
    "/players/{externalPlayerId}/debits": {
      "post": {
        "operationId": "debitPlayer",
        "summary": "Descontar fichas del jugador",
        "tags": [
          "Fichas"
        ],
        "x-required-scope": "credits:write",
        "description": "Scope requerido: credits:write. Toda llamada de backend requiere los cuatro encabezados HMAC. Devuelve a la billetera prepaga fichas del saldo del jugador (retiro de fichas o cash-out). Misma idempotencia que las entregas: una intención usa una clave y bytes exactos; reintentar con timestamp/nonce/firma nuevos y la misma clave, método, ruta, query y body. Una clave ya usada por una entrega responde 409 idempotency_conflict. No reserva fichas: la billetera aumenta sólo cuando el descuento queda COMPLETED. Un resultado incierto queda UNKNOWN hasta su conciliación y nunca se reenvía. Saldo del jugador insuficiente o descuento no enviado: REJECTED (422) sin cambios en la billetera.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "externalPlayerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]{2,127}$",
              "minLength": 3,
              "maxLength": 128,
              "examples": [
                "jugador-123"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Identidad de la intención. Persistir antes de enviar. En una entrega, reutilizar la misma clave, método, path, query y body en reintentos; renovar nonce y firma.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$",
              "minLength": 8,
              "maxLength": 128,
              "examples": [
                "debit-order-7001"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Descuento COMPLETED: fichas acreditadas en la billetera prepaga. replayed indica repetición de la intención.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DebitResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000003",
                    "kind": "DEBIT",
                    "state": "COMPLETED",
                    "amountCredits": "25.50",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "202": {
            "description": "RESERVED o UNKNOWN. La billetera todavía no cambió. Consultar el UUID; no iniciar otra intención.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DebitResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000003",
                    "kind": "DEBIT",
                    "state": "UNKNOWN",
                    "amountCredits": "25.50",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "description": "Descuento REJECTED: saldo del jugador insuficiente o descuento no enviado. Billetera sin cambios. Esta respuesta contiene operation, no el formato general de error.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DebitResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000003",
                    "kind": "DEBIT",
                    "state": "REJECTED",
                    "amountCredits": "25.50",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  },
                  "replayed": false
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DebitRequest"
              },
              "example": {
                "amountCredits": "25.50",
                "currency": "CRD"
              }
            }
          }
        }
      }
    },
    "/games": {
      "get": {
        "operationId": "listGames",
        "summary": "Consultar catálogo paginado",
        "tags": [
          "Juegos"
        ],
        "x-required-scope": "games:read",
        "description": "Scope requerido: games:read. Toda llamada de backend requiere los cuatro encabezados HMAC. Avanzar offset + count mientras hasMore. Si cambia revision, reiniciar paginación. IDs opacos. Las portadas autorizadas se entregan en coverStaticUrl y coverAnimatedUrl; cada campo puede ser null. coverAnimatedUrl es la portada animada oficial del juego y, si no la tiene, una animación generada por Provibet a partir de su portada oficial; coverAnimatedSource indica cuál es (official, generated o null). Filtros opcionales: studio (nombre público o su clave studioId) y cover=animated (solo juegos con portada animada oficial). Orden: primero los juegos con portada animada oficial, luego por título; las animaciones generadas no cambian el orden ni el filtro cover=animated. Cada juego informa source: primary (proveedor principal) o partner (socio integrado).",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Búsqueda por título (contiene, sin distinguir mayúsculas). Se consideran los primeros 100 caracteres."
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "slots",
                "live",
                "crash",
                "table",
                "sports"
              ]
            },
            "description": "Categoría de lobby: slots, live, crash, table o sports."
          },
          {
            "name": "studio",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "description": "Estudio: nombre público (p. ej. \"Pragmatic Play\") o su clave studioId (p. ej. pragmaticplay). Se compara por la clave normalizada."
          },
          {
            "name": "cover",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "animated"
              ]
            },
            "description": "animated: solo juegos con portada animada oficial (coverAnimatedSource official). Las animaciones generadas por Provibet no cuentan para este filtro. Otro valor responde 400 invalid_cover_filter."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Tamaño de página, 1 a 200. Fuera de rango o no entero: 400 invalid_pagination."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "default": 0
            },
            "description": "Desplazamiento, 0 a 1000000. Fuera de rango o no entero: 400 invalid_pagination."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo habilitado. No se garantiza un total constante.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamesResponse"
                },
                "example": {
                  "revision": "2026-09-24T09:05:12.000Z",
                  "total": 1,
                  "count": 1,
                  "limit": 50,
                  "offset": 0,
                  "hasMore": false,
                  "games": [
                    {
                      "id": "game_0123456789abcdef0123456789abcdef01234567",
                      "title": "Mega Roulette",
                      "category": "live",
                      "studio": "Pragmatic Play Live",
                      "studioId": "pragmaticplaylive",
                      "coverStaticUrl": "https://api.provibet.com/media/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "coverAnimatedUrl": null,
                      "coverAnimatedSource": null,
                      "source": "primary"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/studios": {
      "get": {
        "operationId": "listStudios",
        "summary": "Estudios y categorías del catálogo",
        "tags": [
          "Juegos"
        ],
        "x-required-scope": "games:read",
        "description": "Scope requerido: games:read. Estudios con juegos habilitados y cantidad por categoría, para armar filtros del lobby. Requiere los encabezados HMAC.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Facetas del catálogo habilitado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudiosResponse"
                },
                "example": {
                  "studios": [
                    {
                      "id": "pragmaticplay",
                      "name": "Pragmatic Play",
                      "games": 614
                    }
                  ],
                  "categories": [
                    {
                      "id": "slots",
                      "games": 3212
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/games/{publicGameId}/launch": {
      "post": {
        "operationId": "launchGame",
        "summary": "Solicitar apertura de juego",
        "tags": [
          "Juegos"
        ],
        "x-required-scope": "games:launch",
        "description": "Scope requerido: games:launch. Toda llamada de backend requiere los cuatro encabezados HMAC. Usar sólo IDs reales del catálogo y jugadores existentes. Abrir URL exacta antes de expiresAt. El ticket en fragmento se canjea una sola vez por la página de apertura. No asumir URL idéntica al repetir ni TTL fijo. Revocación local desde administración; no garantiza cierre de una experiencia remota ya cargada. No enviar credenciales HMAC al navegador.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "publicGameId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ID opaco recibido del catálogo."
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatorio en todo POST (8 a 128 caracteres) y parte de la firma. En este endpoint no deduplica: usá un valor nuevo por solicitud.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$",
              "minLength": 8,
              "maxLength": 128,
              "examples": [
                "launch-7f3a9c21"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "URL temporal de apertura.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LaunchResponse"
                },
                "example": {
                  "launch": {
                    "gameId": "game_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                    "url": "https://api.provibet.com/play/#tenant=CUENTA_OPACA&ticket=TICKET_TEMPORAL",
                    "expiresAt": "2026-09-23T12:03:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LaunchRequest"
              },
              "example": {
                "externalPlayerId": "jugador-123",
                "device": "mobile",
                "locale": "es-AR"
              }
            }
          }
        }
      }
    },
    "/operations/{operationId}": {
      "get": {
        "operationId": "getOperation",
        "summary": "Consultar una operación financiera",
        "tags": [
          "Operaciones"
        ],
        "x-required-scope": "operations:read",
        "description": "Scope requerido: operations:read. Toda llamada de backend requiere los cuatro encabezados HMAC. Sólo operaciones propias de la familia de credenciales, conservada en rotación. Estados terminales COMPLETED y REJECTED. kind distingue entregas (CREDIT) de descuentos (DEBIT). No incluye lista pública ni búsqueda por externalReference.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "operationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Consulta respondida. Leer state; HTTP 200 no implica estado COMPLETED.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                },
                "example": {
                  "operation": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "kind": "CREDIT",
                    "state": "COMPLETED",
                    "amountCredits": "50.00",
                    "currency": "CRD",
                    "createdAt": "2026-09-23T12:00:00.000Z",
                    "updatedAt": "2026-09-23T12:00:01.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/play/": {
      "servers": [
        {
          "url": "https://api.provibet.com",
          "description": "Origen previsto de la página de apertura."
        }
      ],
      "get": {
        "operationId": "openLaunchPage",
        "summary": "Página de apertura del jugador",
        "tags": [
          "Navegador"
        ],
        "security": [],
        "description": "Destino de launch.url: https://api.provibet.com/play/#tenant=CUENTA_OPACA&ticket=TICKET_TEMPORAL. Abrir la URL exacta en el navegador del jugador antes de expiresAt; no construirla. El fragmento no viaja al servidor: la página lo quita de la barra de direcciones antes de cualquier solicitud, valida su formato, lo canjea una sola vez con POST /session/exchange en el mismo origen y reemplaza la ubicación por playUrl (/session/play/{sessionId}), que muestra el juego a pantalla completa. Si el ticket venció, ya se usó o no es válido, informa \"El enlace de juego expiró o no es válido. Volvé a abrir el juego desde tu casino.\"; solicitar entonces una nueva apertura. Sin almacenamiento local ni recursos de terceros. La página es neutra y puede embeberse en un iframe solo desde los dominios del revendedor registrados por Provibet (CSP frame-ancestors). Al salir del juego se envía window.postMessage({type:\"provibet:game-exit\",version:1}) a la ventana contenedora.",
        "responses": {
          "200": {
            "description": "Página HTML estática de apertura; el canje ocurre en el navegador.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/media/{mediaId}": {
      "servers": [
        {
          "url": "https://api.provibet.com",
          "description": "Origen de medios del catálogo."
        }
      ],
      "get": {
        "operationId": "getMedia",
        "summary": "Portada del catálogo",
        "tags": [
          "Navegador"
        ],
        "security": [],
        "description": "Público, sin HMAC. Usar sólo las URLs recibidas en coverStaticUrl / coverAnimatedUrl; no construirlas. Respuesta con Cache-Control: public, max-age=86400, immutable. Cuota propia por IP de origen: 2400 solicitudes GET o HEAD por minuto, que no descuentan de las 360 por minuto de la apertura.",
        "parameters": [
          {
            "in": "path",
            "name": "mediaId",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Imagen (WebP, PNG, JPEG o GIF según el recurso).",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "media_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          }
        }
      }
    },
    "/session/exchange": {
      "servers": [
        {
          "url": "https://api.provibet.com",
          "description": "Origen previsto de la página de apertura."
        }
      ],
      "post": {
        "operationId": "exchangeLaunchTicket",
        "summary": "Canjear ticket de un solo uso desde la página de apertura",
        "tags": [
          "Navegador"
        ],
        "security": [],
        "description": "Gestionado por /play/. Requiere Origin exacto del host de apertura, no HMAC. Ticket del fragmento de launch.url; no repetir el ticket tras éxito. Establece cookies Secure HttpOnly SameSite=Lax host-only. No implica cierre de una experiencia remota al revocar la sesión local.",
        "parameters": [
          {
            "in": "header",
            "name": "Origin",
            "required": true,
            "schema": {
              "type": "string",
              "const": "https://api.provibet.com"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionExchangeRequest"
              },
              "example": {
                "tenantId": "00000000-0000-4000-8000-000000000002",
                "ticket": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sesión establecida mediante cookies.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionExchangeResponse"
                },
                "example": {
                  "session": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "expiresAt": "2026-09-23T12:30:00.000Z"
                  },
                  "playUrl": "/session/play/00000000-0000-4000-8000-000000000001"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          }
        }
      }
    },
    "/session/play/{sessionId}": {
      "servers": [
        {
          "url": "https://api.provibet.com",
          "description": "Origen previsto de la página de apertura."
        }
      ],
      "get": {
        "operationId": "openSessionPage",
        "summary": "Mostrar juego dentro de la sesión de navegador",
        "tags": [
          "Navegador"
        ],
        "security": [],
        "description": "Requiere las cookies HttpOnly emitidas por el canje. No es un endpoint público sin autenticación. La página responde HTML; no devuelve un objeto JSON de apertura. No construir ni modificar cookies.",
        "responses": {
          "200": {
            "description": "Página de juego para la sesión válida.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          }
        },
        "parameters": [
          {
            "in": "path",
            "name": "sessionId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/site/layout": {
      "get": {
        "operationId": "getSiteLayout",
        "summary": "Diseño publicado del sitio",
        "tags": [
          "Sitio"
        ],
        "x-required-scope": "games:read",
        "description": "Scope requerido: games:read. Documento de diseño del sitio de tu integración (banners, popups, menú, pie, chips y listas curadas, orden de secciones y productos), publicado desde la administración. Solo se entrega la versión publicada, nunca un borrador. Los juegos se referencian por su id opaco (el mismo de /games); los medios como asset:<nombre> (recurso propio de tu sitio) o media:<mediaId> (servido en https://api.provibet.com/media/{mediaId}); los enlaces son rutas relativas de tu sitio o https://wa.me/+<número>. Cacheá la respuesta y revalidá con If-None-Match: si no cambió, responde 304 sin cuerpo. Un juego puede dejar de estar habilitado después de publicado: ocultá los ids que no figuren en /games. Requiere los encabezados HMAC.",
        "parameters": [
          {
            "name": "X-B2B-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix UTC en segundos, 10 dígitos. Debe estar dentro de ±300 s de la hora del servidor.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{10}$"
            }
          },
          {
            "name": "X-B2B-Nonce",
            "in": "header",
            "required": true,
            "description": "Aleatorio, de un solo uso por credencial. Nuevo en cada intento HTTP, también en reintentos.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Opcional, para tus propios registros. La API no lo reutiliza: cada respuesta trae su propio X-Request-Id (UUID).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,128}$"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "ETag de una respuesta anterior. Si coincide con la versión publicada, 304 sin cuerpo.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "site",
            "in": "query",
            "required": false,
            "description": "Dominio del sitio (p. ej. argenplay.fun). Opcional si tu integración tiene un solo sitio publicado; con varios es obligatorio (400 site_required). Formato inválido: 400 invalid_site.",
            "schema": {
              "type": "string",
              "maxLength": 253,
              "pattern": "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Versión publicada del documento. Las respuestas admiten campos adicionales para evolución compatible.",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              },
              "ETag": {
                "description": "Validador fuerte de la versión publicada.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "\"pv3-0123456789abcdef0123456789abcdef\""
                  ]
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "private, no-cache"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteLayoutResponse"
                },
                "example": {
                  "layout": {
                    "site": "argenplay.fun",
                    "version": 3,
                    "status": "published",
                    "publishedAt": "2026-09-24T12:00:00.000Z",
                    "document": {
                      "schemaVersion": 1,
                      "supportUrl": "https://wa.me/+541134875908",
                      "banners": {
                        "home": [
                          {
                            "type": "image",
                            "desktopMediaId": "asset:banner-home-1.webp",
                            "mobileMediaId": "asset:banner-home-1-m.webp",
                            "alt": "Bienvenida",
                            "active": true,
                            "order": 0
                          }
                        ],
                        "slots": []
                      },
                      "popups": {
                        "slots": {
                          "mediaId": "asset:popup-slots.webp",
                          "delayS": 5,
                          "frequencyH": 24,
                          "active": true
                        }
                      },
                      "nav": [
                        {
                          "label": "INICIO",
                          "route": "/",
                          "active": true
                        },
                        {
                          "label": "SLOTS",
                          "route": "/product/games-general/luxury-n",
                          "active": true
                        }
                      ],
                      "home": {
                        "categoryChips": [
                          {
                            "label": "NUEVOS",
                            "iconMediaId": "asset:icon-new.png",
                            "source": {
                              "type": "query",
                              "collection": "new"
                            },
                            "hidesRows": false,
                            "active": true
                          }
                        ],
                        "providerChips": [],
                        "providerModal": [],
                        "rows": [
                          [
                            "game_0123456789abcdef0123456789abcdef01234567"
                          ]
                        ]
                      },
                      "roulette": {
                        "gameId": "game_0123456789abcdef0123456789abcdef01234567"
                      }
                    }
                  }
                }
              }
            }
          },
          "304": {
            "description": "Sin cambios desde el ETag enviado. Sin cuerpo.",
            "headers": {
              "ETag": {
                "description": "Validador de la versión publicada.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "description": "Tu integración no tiene un diseño publicado (site_layout_not_published).",
            "headers": {
              "X-Request-Id": {
                "description": "Identificador de correlación de la API.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "site_layout_not_published",
                    "requestId": "correlacion-12345678"
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "B2BPublicKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-B2B-Key",
        "description": "publicKey de integración: b2b_ seguido de 43 caracteres base64url. No autentica sola: HMAC, timestamp y nonce también son obligatorios."
      },
      "B2BSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-B2B-Signature",
        "description": "Hex HMAC-SHA256(secret, cadena canónica de nueve líneas): B2B-HMAC-SHA256, publicKey, timestamp, nonce, METHOD, path exacto, query RFC3986 ordenado por nombre/valor, Idempotency-Key o vacío, SHA256(body exacto). Separador LF; sin salto final. Hexadecimal en minúsculas (64 caracteres); se acepta prefijo sha256=. Nunca enviar el secreto."
      }
    },
    "schemas": {
      "CreditQuantity": {
        "type": "string",
        "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{2}$",
        "description": "Decimal no negativo como string con dos posiciones. No convertir a punto flotante.",
        "examples": [
          "9500.00"
        ]
      },
      "CreditAmount": {
        "type": "string",
        "pattern": "^(?:0|[1-9][0-9]*)(?:\\.[0-9]{1,2})?$",
        "description": "Decimal positivo como string con hasta dos posiciones, sin exponentes, signos ni ceros iniciales (máximo 21 caracteres). Mayor que cero y hasta 10000000000.00 CRD.",
        "examples": [
          "50",
          "50.1",
          "50.25"
        ]
      },
      "ExternalPlayerId": {
        "type": "string",
        "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]{2,127}$",
        "minLength": 3,
        "maxLength": 128,
        "examples": [
          "jugador-123"
        ]
      },
      "Wallet": {
        "type": "object",
        "properties": {
          "currency": {
            "const": "CRD",
            "type": "string"
          },
          "availableCredits": {
            "$ref": "#/components/schemas/CreditQuantity"
          },
          "reservedCredits": {
            "$ref": "#/components/schemas/CreditQuantity"
          },
          "version": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$"
          }
        },
        "required": [
          "currency",
          "availableCredits",
          "reservedCredits",
          "version"
        ]
      },
      "WalletResponse": {
        "type": "object",
        "properties": {
          "wallet": {
            "$ref": "#/components/schemas/Wallet"
          }
        },
        "required": [
          "wallet"
        ]
      },
      "CreatePlayerRequest": {
        "type": "object",
        "properties": {
          "externalPlayerId": {
            "$ref": "#/components/schemas/ExternalPlayerId"
          },
          "displayName": {
            "type": "string",
            "maxLength": 100
          }
        },
        "required": [
          "externalPlayerId"
        ],
        "additionalProperties": false
      },
      "PlayerResponse": {
        "type": "object",
        "properties": {
          "player": {
            "type": "object",
            "properties": {
              "externalPlayerId": {
                "$ref": "#/components/schemas/ExternalPlayerId"
              }
            },
            "required": [
              "externalPlayerId"
            ]
          }
        },
        "required": [
          "player"
        ]
      },
      "BalanceResponse": {
        "type": "object",
        "properties": {
          "player": {
            "type": "object",
            "properties": {
              "externalPlayerId": {
                "$ref": "#/components/schemas/ExternalPlayerId"
              },
              "balanceCredits": {
                "$ref": "#/components/schemas/CreditQuantity"
              },
              "currency": {
                "type": "string",
                "const": "CRD"
              }
            },
            "required": [
              "externalPlayerId",
              "balanceCredits",
              "currency"
            ]
          }
        },
        "required": [
          "player"
        ]
      },
      "CreditRequest": {
        "type": "object",
        "properties": {
          "amountCredits": {
            "$ref": "#/components/schemas/CreditAmount"
          },
          "currency": {
            "type": "string",
            "const": "CRD",
            "default": "CRD"
          }
        },
        "required": [
          "amountCredits"
        ],
        "additionalProperties": false
      },
      "Operation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "CREDIT",
              "DEBIT"
            ],
            "description": "CREDIT: fichas de la billetera prepaga al jugador. DEBIT: fichas del jugador a la billetera prepaga."
          },
          "state": {
            "type": "string",
            "enum": [
              "RESERVED",
              "COMPLETED",
              "REJECTED",
              "UNKNOWN"
            ],
            "description": "RESERVED → COMPLETED | REJECTED | UNKNOWN; UNKNOWN → COMPLETED por conciliación. UNKNOWN nunca se libera automáticamente. COMPLETED y REJECTED son terminales."
          },
          "amountCredits": {
            "$ref": "#/components/schemas/CreditQuantity"
          },
          "currency": {
            "type": "string",
            "const": "CRD"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "kind",
          "state",
          "amountCredits",
          "currency",
          "createdAt",
          "updatedAt"
        ]
      },
      "CreditResponse": {
        "type": "object",
        "properties": {
          "operation": {
            "$ref": "#/components/schemas/Operation"
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "operation",
          "replayed"
        ]
      },
      "DebitRequest": {
        "type": "object",
        "properties": {
          "amountCredits": {
            "$ref": "#/components/schemas/CreditAmount"
          },
          "currency": {
            "type": "string",
            "const": "CRD",
            "default": "CRD"
          }
        },
        "required": [
          "amountCredits"
        ],
        "additionalProperties": false
      },
      "DebitResponse": {
        "type": "object",
        "properties": {
          "operation": {
            "$ref": "#/components/schemas/Operation"
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "operation",
          "replayed"
        ]
      },
      "OperationResponse": {
        "type": "object",
        "properties": {
          "operation": {
            "$ref": "#/components/schemas/Operation"
          }
        },
        "required": [
          "operation"
        ]
      },
      "Game": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador opaco. Obtenerlo del catálogo; no construirlo."
          },
          "title": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "slots",
              "live",
              "crash",
              "table",
              "sports"
            ],
            "description": "Categoría de lobby: slots (tragamonedas), live (casino en vivo), crash, table (mesa) o sports (deportes)."
          },
          "coverStaticUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "URL https://api.provibet.com/media/{mediaId} de la portada estática, o null."
          },
          "coverAnimatedUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "URL https://api.provibet.com/media/{mediaId} de la portada animada: la oficial si el juego la tiene; si no, una animación generada por Provibet a partir de su portada oficial; o null. coverAnimatedSource indica cuál es."
          },
          "coverAnimatedSource": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "official",
              "generated",
              null
            ],
            "description": "Origen de coverAnimatedUrl. official: portada animada oficial del juego. generated: animación generada por Provibet a partir de la portada estática oficial (WebP animado liviano cuyo primer cuadro es esa portada); se entrega solo cuando no hay oficial. null: sin portada animada. El orden del catálogo («animadas primero») y el filtro cover=animated consideran solo las oficiales."
          },
          "studio": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nombre público del estudio del juego."
          },
          "studioId": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[a-z0-9]{1,60}$",
            "description": "Clave del estudio para filtrar (?studio=)."
          },
          "source": {
            "type": "string",
            "enum": [
              "primary",
              "partner"
            ],
            "description": "Origen del juego (agregado el 26/09/2026): primary = catálogo del proveedor principal; partner = catálogo de un socio integrado (por ejemplo 4Salas). Sirve para agrupar o contar proveedores en el lobby; no cambia la apertura ni el saldo. Ignorá valores que no conozcas."
          }
        },
        "required": [
          "id",
          "title",
          "category",
          "coverStaticUrl",
          "coverAnimatedUrl",
          "coverAnimatedSource",
          "studio",
          "studioId",
          "source"
        ]
      },
      "GamesResponse": {
        "type": "object",
        "properties": {
          "revision": {
            "type": "string",
            "description": "Revisión opaca del catálogo filtrado (hoy, fecha ISO de su último cambio; \"empty\" si no hay juegos). Si cambia entre páginas, reiniciar la sincronización."
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000
          },
          "hasMore": {
            "type": "boolean"
          },
          "games": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          }
        },
        "required": [
          "revision",
          "total",
          "count",
          "limit",
          "offset",
          "hasMore",
          "games"
        ]
      },
      "LaunchRequest": {
        "type": "object",
        "properties": {
          "externalPlayerId": {
            "$ref": "#/components/schemas/ExternalPlayerId"
          },
          "device": {
            "type": "string",
            "enum": [
              "mobile",
              "desktop"
            ],
            "description": "Opcional (mobile o desktop). Se valida pero hoy no modifica la apertura: el juego se adapta al dispositivo."
          },
          "locale": {
            "type": "string",
            "pattern": "^[a-z]{2}(?:-[A-Z]{2})?$",
            "examples": [
              "es-AR"
            ],
            "description": "Idioma preferido: dos letras en minúscula, opcionalmente con región en mayúscula (es, es-AR, en, en-US, pt-BR). Por defecto es. Cada juego usa su idioma por defecto si no admite el pedido."
          }
        },
        "required": [
          "externalPlayerId"
        ],
        "additionalProperties": false
      },
      "LaunchResponse": {
        "type": "object",
        "properties": {
          "launch": {
            "type": "object",
            "properties": {
              "gameId": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "URL temporal opaca. Abrir exactamente como fue entregada; no registrarla completa ni compartirla entre jugadores."
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "description": "Vencimiento del ticket de un solo uso (hoy, 3 minutos después de emitido)."
              }
            },
            "required": [
              "gameId",
              "url",
              "expiresAt"
            ]
          }
        },
        "required": [
          "launch"
        ]
      },
      "SessionExchangeRequest": {
        "type": "object",
        "properties": {
          "tenantId": {
            "type": "string",
            "format": "uuid"
          },
          "ticket": {
            "type": "string",
            "minLength": 43,
            "maxLength": 43
          }
        },
        "required": [
          "tenantId",
          "ticket"
        ],
        "additionalProperties": false
      },
      "SessionExchangeResponse": {
        "type": "object",
        "properties": {
          "session": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "id",
              "expiresAt"
            ]
          },
          "playUrl": {
            "type": "string",
            "description": "Ruta opaca /session/play/{sessionId} recibida; no construirla."
          },
          "target": {
            "type": "string",
            "format": "uri",
            "description": "Destino del juego para abrirlo dentro de /play/ (también embebido). Solo se entrega en el canje de un ticket de un único uso."
          }
        },
        "required": [
          "session",
          "playUrl"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "requestId": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "requestId"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Studio": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9]{1,60}$",
            "description": "Clave del estudio, p. ej. pragmaticplay, playngo, hacksawgaming."
          },
          "name": {
            "type": "string",
            "description": "Nombre público del estudio."
          },
          "games": {
            "type": "integer",
            "minimum": 0,
            "description": "Cantidad de juegos habilitados del estudio."
          }
        },
        "required": [
          "id",
          "name",
          "games"
        ],
        "description": "Estudio con juegos habilitados. id es la clave para ?studio= en GET /games (minúsculas y dígitos del nombre público)."
      },
      "CategoryCount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "slots",
              "live",
              "crash",
              "table",
              "sports"
            ],
            "description": "slots: tragamonedas · live: casino en vivo · crash: juegos crash · table: juegos de mesa · sports: apuestas deportivas."
          },
          "games": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "id",
          "games"
        ],
        "description": "Categoría de lobby y su cantidad de juegos habilitados."
      },
      "StudiosResponse": {
        "type": "object",
        "properties": {
          "studios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Studio"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryCount"
            }
          }
        },
        "required": [
          "studios",
          "categories"
        ],
        "description": "Facetas del catálogo habilitado, ordenadas por cantidad de juegos (descendente)."
      },
      "SiteLayoutDocument": {
        "type": "object",
        "description": "Documento de diseño publicado. Juegos: ids opacos game_<40 hex>. Medios: asset:<nombre> o media:<mediaId>. Enlaces: rutas relativas (/…) o https://wa.me/+<número>.",
        "properties": {
          "schemaVersion": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "supportUrl": {
            "type": "string",
            "pattern": "^https://wa\\.me/\\+[0-9]{8,15}$"
          },
          "banners": {
            "type": "object",
            "description": "home[] y slots[]: {type image|video, desktopMediaId, mobileMediaId?, alt, link?, active, order}."
          },
          "popups": {
            "type": "object",
            "description": "slots? y live?: {mediaId, alt?, link?, delayS, frequencyH, active}."
          },
          "nav": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "{label, route, iconMediaId?, active}."
            }
          },
          "footer": {
            "type": "object",
            "description": "{logoMediaId?, columns[{title, links[{label, href}]}], copyright[], notice?}."
          },
          "home": {
            "type": "object",
            "description": "{categoryChips[{label, iconMediaId, source, hidesRows, active}], providerChips[{label, iconMediaId, studio, active}], providerModal[], rows[ids[]]}. source: {type:\"list\", ids[]} o {type:\"query\", q?, category?, collection?, studio?}."
          },
          "slots": {
            "type": "object",
            "description": "{categoryChips[], providerChips[], sectionOrder[studioId], perSectionLimit, initialSections, step}."
          },
          "live": {
            "type": "object",
            "description": "{initial ids[], categories[{label, iconMediaId, ids[]}], chips[{label, logoMediaId, source, pills[{label, ids[]}]}]}."
          },
          "crash": {
            "type": "object",
            "description": "{slides[{bgMediaId, items[{mediaId, top, left, widthVw, anim}], stripColor}], list ids[]}."
          },
          "roulette": {
            "type": "object",
            "description": "{gameId}."
          },
          "sports": {
            "type": "object",
            "description": "{gameId}."
          },
          "sportsLive": {
            "type": "object",
            "description": "{gameId, deepLink?}."
          },
          "horses": {
            "type": "object",
            "description": "{gameId}."
          }
        },
        "required": [
          "schemaVersion"
        ]
      },
      "SiteLayoutResponse": {
        "type": "object",
        "properties": {
          "layout": {
            "type": "object",
            "properties": {
              "site": {
                "type": "string"
              },
              "version": {
                "type": "integer",
                "minimum": 1
              },
              "status": {
                "type": "string",
                "enum": [
                  "published"
                ]
              },
              "publishedAt": {
                "type": "string",
                "format": "date-time"
              },
              "document": {
                "$ref": "#/components/schemas/SiteLayoutDocument"
              }
            },
            "required": [
              "site",
              "version",
              "status",
              "publishedAt",
              "document"
            ]
          }
        },
        "required": [
          "layout"
        ]
      }
    },
    "responses": {
      "Error400": {
        "description": "Solicitud inválida. Códigos: invalid_request, invalid_json, invalid_pagination, invalid_cover_filter, invalid_amount, idempotency_key_required, duplicate_authentication_header, invalid_request_path, invalid_site, site_required. Corregir antes de reintentar.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_request",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error401": {
        "description": "Autenticación rechazada. Códigos: invalid_authentication_headers, request_timestamp_outside_window, invalid_client_credentials, invalid_request_signature.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_request_signature",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error403": {
        "description": "Sin autorización. Códigos: source_ip_not_allowed, source_ip_unavailable, insufficient_scope, origin_not_allowed (canje de navegador).",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "source_ip_not_allowed",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error404": {
        "description": "Recurso inexistente o ajeno a tu integración. Códigos: player_not_found, game_not_found, operation_not_found, site_layout_not_published, not_found.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "player_not_found",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error409": {
        "description": "Conflicto. Códigos: nonce_replayed_or_invalid, idempotency_conflict, insufficient_credits, player_not_ready, player_initialization_pending, player_initialization_review.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "idempotency_conflict",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error429": {
        "description": "Límite de frecuencia: 120 solicitudes por minuto por clave pública y 360 por minuto por IP de origen, que protegen también la apertura (/play/ y /session/exchange). Las portadas (GET o HEAD /media/{mediaId}) tienen su propia cuota de 2400 por minuto por IP de origen y no descuentan de esas 360. Ventanas fijas de 60 s. No incluye Retry-After: aplicar backoff exponencial con jitter.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limit_exceeded",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error500": {
        "description": "Error no resuelto (service_unavailable). En una entrega o descuento el resultado puede ser incierto: consultar la operación o reintentar con la misma Idempotency-Key.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "service_unavailable",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      },
      "Error503": {
        "description": "Temporalmente no disponible. Códigos: service_temporarily_unavailable, financial_operations_disabled, registration_unavailable, game_launch_unavailable, game_launch_disabled, player_initialization_review, service_busy.",
        "headers": {
          "X-Request-Id": {
            "description": "Identificador de correlación de la API.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "schema": {
              "type": "string",
              "examples": [
                "no-store"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "service_temporarily_unavailable",
                "requestId": "correlacion-12345678"
              }
            }
          }
        }
      }
    }
  }
}
