{
  "openapi": "3.1.0",
  "info": {
    "title": "OrbiSportbook operator seamless wallet callbacks",
    "version": "2026-10-18",
    "description": "Operator-owned endpoints, not OrbiSportbook API endpoints. Four-callback contract. Timeout default 5 seconds. Money callbacks must echo txId and currency. No transaction-status endpoint in this revision. Integration keys and wallet secrets are different credentials."
  },
  "servers": [
    {
      "url": "https://wallet.operator.example/sportsbook",
      "description": "Replace with the operator HTTPS walletUrl."
    }
  ],
  "paths": {
    "/balance": {
      "post": {
        "operationId": "balance",
        "summary": "Read available balance",
        "description": "The operator implements this callback. HMAC signs the exact raw request bytes. Retries keep the same financial identity. Rollback must tombstone an unseen original debit; correction is not rollback.",
        "parameters": [
          {
            "name": "X-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "description": "Unix seconds; accept at most +/-300 seconds clock skew."
          },
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-fA-F0-9]{64}$"
            },
            "description": "hex HMAC-SHA256(secret, timestamp + \".\" + exact raw JSON body), compare in constant time."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BalanceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durably applied (or identical stored replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Definitive refusal; no movement. Never use for temporary failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient funds. Persist refusal; do not debit partially or collect future deposits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Other 2xx require the complete success envelope. Other 4xx except 408/429 require the error envelope to be definitive. 408,429,5xx, malformed or mismatching replies and timeouts are unknown."
          }
        }
      }
    },
    "/debit": {
      "post": {
        "operationId": "debit",
        "summary": "Debit stake or correction without overdraft",
        "description": "The operator implements this callback. HMAC signs the exact raw request bytes. Retries keep the same financial identity. Rollback must tombstone an unseen original debit; correction is not rollback.",
        "parameters": [
          {
            "name": "X-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "description": "Unix seconds; accept at most +/-300 seconds clock skew."
          },
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-fA-F0-9]{64}$"
            },
            "description": "hex HMAC-SHA256(secret, timestamp + \".\" + exact raw JSON body), compare in constant time."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DebitRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durably applied (or identical stored replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Definitive refusal; no movement. Never use for temporary failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient funds. Persist refusal; do not debit partially or collect future deposits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Other 2xx require the complete success envelope. Other 4xx except 408/429 require the error envelope to be definitive. 408,429,5xx, malformed or mismatching replies and timeouts are unknown."
          }
        }
      }
    },
    "/credit": {
      "post": {
        "operationId": "credit",
        "summary": "Credit payout, refund, cash-out or correction",
        "description": "The operator implements this callback. HMAC signs the exact raw request bytes. Retries keep the same financial identity. Rollback must tombstone an unseen original debit; correction is not rollback.",
        "parameters": [
          {
            "name": "X-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "description": "Unix seconds; accept at most +/-300 seconds clock skew."
          },
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-fA-F0-9]{64}$"
            },
            "description": "hex HMAC-SHA256(secret, timestamp + \".\" + exact raw JSON body), compare in constant time."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durably applied (or identical stored replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Definitive refusal; no movement. Never use for temporary failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient funds. Persist refusal; do not debit partially or collect future deposits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Other 2xx require the complete success envelope. Other 4xx except 408/429 require the error envelope to be definitive. 408,429,5xx, malformed or mismatching replies and timeouts are unknown."
          }
        }
      }
    },
    "/rollback": {
      "post": {
        "operationId": "rollback",
        "summary": "Reverse or permanently prevent a stake debit",
        "description": "The operator implements this callback. HMAC signs the exact raw request bytes. Retries keep the same financial identity. Rollback must tombstone an unseen original debit; correction is not rollback.",
        "parameters": [
          {
            "name": "X-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "description": "Unix seconds; accept at most +/-300 seconds clock skew."
          },
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-fA-F0-9]{64}$"
            },
            "description": "hex HMAC-SHA256(secret, timestamp + \".\" + exact raw JSON body), compare in constant time."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RollbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durably applied (or identical stored replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Definitive refusal; no movement. Never use for temporary failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient funds. Persist refusal; do not debit partially or collect future deposits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Other 2xx require the complete success envelope. Other 4xx except 408/429 require the error envelope to be definitive. 408,429,5xx, malformed or mismatching replies and timeouts are unknown."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BalanceRequest": {
        "type": "object",
        "required": [
          "txId",
          "playerId",
          "amount",
          "currency",
          "kind"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "playerId": {
            "type": "string",
            "minLength": 1,
            "description": "Operator external player ID scoped to this tenant."
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Minor units, never floating-point major units.",
            "const": 0
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "balance"
            ]
          },
          "betId": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "DebitRequest": {
        "type": "object",
        "required": [
          "txId",
          "playerId",
          "amount",
          "currency",
          "kind"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "playerId": {
            "type": "string",
            "minLength": 1,
            "description": "Operator external player ID scoped to this tenant."
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "description": "Minor units, never floating-point major units."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "bet",
              "resettle",
              "adjustment"
            ]
          },
          "betId": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "CreditRequest": {
        "type": "object",
        "required": [
          "txId",
          "playerId",
          "amount",
          "currency",
          "kind"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "playerId": {
            "type": "string",
            "minLength": 1,
            "description": "Operator external player ID scoped to this tenant."
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Minor units, never floating-point major units."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "payout",
              "refund",
              "cashout",
              "resettle",
              "adjustment"
            ]
          },
          "betId": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "RollbackRequest": {
        "type": "object",
        "required": [
          "txId",
          "playerId",
          "amount",
          "currency",
          "kind",
          "ref"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "playerId": {
            "type": "string",
            "minLength": 1,
            "description": "Operator external player ID scoped to this tenant."
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Minor units, never floating-point major units."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "rollback"
            ]
          },
          "betId": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "MoneyResponse": {
        "type": "object",
        "required": [
          "txId",
          "balance",
          "currency"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "balance": {
            "type": "integer",
            "format": "int64",
            "description": "Balance after the movement. Explicit zero is valid. Required, non-null."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          }
        },
        "description": "txId and currency MUST equal the request values; no error member. Confirm only after durable commit.",
        "not": {
          "required": [
            "error"
          ]
        },
        "additionalProperties": true
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "balance",
          "currency"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Immutable idempotency key; treat as opaque."
          },
          "balance": {
            "type": "integer",
            "format": "int64",
            "description": "Balance after the movement. Explicit zero is valid. Required, non-null."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          }
        },
        "not": {
          "required": [
            "error"
          ]
        },
        "additionalProperties": true
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "type": "string",
                "minLength": 1,
                "pattern": "\\S"
              },
              "message": {
                "type": "string"
              }
            },
            "additionalProperties": true
          }
        },
        "description": "4xx except 408/429 plus this body means no money moved. All other errors are unknown.",
        "additionalProperties": true
      }
    }
  },
  "externalDocs": {
    "url": "https://api.orbisportbook.com/docs/wallet/"
  }
}
