{
  "openapi": "3.1.0",
  "info": {
    "title": "Callisto Games Operator API",
    "version": "1.0.0",
    "summary": "Game catalogue, player sessions, promotions and reconciliation for operators.",
    "description": "Every request under /v1 is signed with HMAC-SHA256: X-Agg-Signature = hex(HMAC_SHA256(secret, X-Agg-Timestamp + \".\" + payload)), where payload is the raw body, or \"METHOD /path?query\" for a request without a body. Timestamps are accepted within 5 minutes. Responses are flat JSON objects in snake_case, without an envelope.",
    "license": {
      "name": "Proprietary: for operators under contract with Callisto Games",
      "identifier": "LicenseRef-Proprietary"
    },
    "contact": {
      "name": "Your Callisto Games account manager"
    }
  },
  "servers": [
    {
      "url": "https://sandbox-api.example.com",
      "description": "Sandbox"
    },
    {
      "url": "https://{production_host}",
      "description": "Production: the host is provided together with production credentials",
      "variables": {
        "production_host": {
          "default": "provided-at-onboarding"
        }
      }
    }
  ],
  "security": [
    {
      "KeyId": [],
      "Timestamp": [],
      "Signature": []
    }
  ],
  "tags": [
    {
      "name": "Getting started",
      "description": "Verify signing and clock"
    },
    {
      "name": "Catalogue",
      "description": "Games, providers, artwork and your rates"
    },
    {
      "name": "Sessions",
      "description": "Open, demo and close game sessions"
    },
    {
      "name": "Promotions",
      "description": "Free-spin grants and jackpots"
    },
    {
      "name": "Reconciliation",
      "description": "Transactions and rounds as our books hold them"
    }
  ],
  "paths": {
    "/v1/ping": {
      "get": {
        "tags": [
          "Getting started"
        ],
        "operationId": "ping",
        "summary": "Verify signing and clock",
        "responses": {
          "200": {
            "description": "Who the key belongs to and our time",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ping"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/games": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "listGames",
        "summary": "Games you may offer, paged by cursor",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/GameType"
            }
          },
          {
            "name": "provider_code",
            "in": "query",
            "description": "One provider, as GET /v1/providers names it",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studio_code",
            "in": "query",
            "deprecated": true,
            "description": "Deprecated: the same filter as provider_code under its older name; wins when both are sent.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2. Leaves out games that cannot be played in that country.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "next_cursor of the previous page",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1..200, default 50. Above 200 is cut to 200; below 1 is VALIDATION_FAILED.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of games",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamesPage"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Bad query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/providers": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "listProviders",
        "summary": "Providers behind the games, whole list",
        "responses": {
          "200": {
            "description": "Every provider in your window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "providers"
                  ],
                  "properties": {
                    "providers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Provider"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/images/{id}": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "getImage",
        "summary": "An image by id (not signed)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Image id from an images URL",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG, JPEG, WebP or GIF, at most 5 MB. Cache-Control: public, max-age=31536000, immutable",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such image"
          }
        }
      }
    },
    "/v1/debug/signature": {
      "post": {
        "tags": [
          "Getting started"
        ],
        "operationId": "checkSignature",
        "summary": "Sandbox only: check a signature step by step (not signed)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key_id",
                  "timestamp",
                  "signature",
                  "method",
                  "path"
                ],
                "properties": {
                  "key_id": {
                    "type": "string",
                    "description": "The X-Agg-Key-Id you sent"
                  },
                  "timestamp": {
                    "type": "string",
                    "description": "The X-Agg-Timestamp you sent, Unix seconds"
                  },
                  "signature": {
                    "type": "string",
                    "description": "The X-Agg-Signature you sent"
                  },
                  "method": {
                    "type": "string",
                    "description": "GET, POST, PUT or DELETE"
                  },
                  "path": {
                    "type": "string",
                    "description": "Path and query exactly as sent"
                  },
                  "body": {
                    "type": "string",
                    "description": "The raw body; absent for a request without one"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every step of the check; never the expected signature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "diagnosis": {
                      "type": "string",
                      "description": "One sentence: what is wrong, or that nothing is"
                    },
                    "key": {
                      "type": "object",
                      "required": [
                        "known"
                      ],
                      "properties": {
                        "known": {
                          "type": "boolean"
                        }
                      }
                    },
                    "timestamp": {
                      "type": "object",
                      "required": [
                        "fresh",
                        "server_time",
                        "window_seconds"
                      ],
                      "properties": {
                        "fresh": {
                          "type": "boolean"
                        },
                        "server_time": {
                          "type": "integer",
                          "description": "Our clock, Unix seconds"
                        },
                        "drift_seconds": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Your timestamp minus ours; null when yours is not a number"
                        },
                        "window_seconds": {
                          "type": "integer"
                        }
                      }
                    },
                    "payload": {
                      "type": "object",
                      "required": [
                        "kind",
                        "bytes"
                      ],
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "body",
                            "request_line"
                          ]
                        },
                        "bytes": {
                          "type": "integer"
                        }
                      }
                    },
                    "canonical_string": {
                      "type": "string",
                      "description": "timestamp + \".\" + payload, exactly what is signed"
                    },
                    "signature": {
                      "type": "object",
                      "required": [
                        "well_formed",
                        "matches"
                      ],
                      "properties": {
                        "well_formed": {
                          "type": "boolean",
                          "description": "64 hex characters; the case is not checked"
                        },
                        "matches": {
                          "type": "boolean"
                        }
                      }
                    },
                    "hints": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not a sandbox installation"
          },
          "422": {
            "description": "The form is incomplete",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "30 checks a minute per key, 60 per caller address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/wallet-validation": {
      "post": {
        "tags": [
          "Wallet integration"
        ],
        "operationId": "startWalletValidation",
        "summary": "Sandbox only: run the wallet test suite against your wallet",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "player_id",
                  "currency"
                ],
                "properties": {
                  "player_id": {
                    "type": "string",
                    "description": "Your test player, with at least 10.00"
                  },
                  "currency": {
                    "type": "string",
                    "description": "The currency of that balance"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The run, queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletValidationRun"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "A run is going, or the hour is used up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_RUN_REFUSED": {
                    "value": {
                      "error": "VALIDATION_RUN_REFUSED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The form is incomplete",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/wallet-validation/{id}": {
      "get": {
        "tags": [
          "Wallet integration"
        ],
        "operationId": "getWalletValidation",
        "summary": "Sandbox only: a run and every case so far",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The run id from the start",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletValidationRun"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not your run, or not a sandbox",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rates": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "getRates",
        "summary": "Your revenue-share percent per provider and band",
        "responses": {
          "200": {
            "description": "Rates as the invoice will apply them",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rates"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "operationId": "createSession",
        "summary": "Open a game for a player",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSession"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Open the game at launch_url",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game cannot be opened for this player",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_RESTRICTED_IN_COUNTRY": {
                    "value": {
                      "error": "GAME_RESTRICTED_IN_COUNTRY",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "PLAYER_BLOCKED": {
                    "value": {
                      "error": "PLAYER_BLOCKED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/games/{id}/demo": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "operationId": "createDemo",
        "summary": "Fun play without a player or wallet",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Game id from GET /v1/games",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDemo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Open the demo at launch_url",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "launch_url"
                  ],
                  "properties": {
                    "launch_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "No demo for this game or player",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NO_DEMO": {
                    "value": {
                      "error": "NO_DEMO",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_RESTRICTED_IN_COUNTRY": {
                    "value": {
                      "error": "GAME_RESTRICTED_IN_COUNTRY",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{id}/close": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "operationId": "closeSession",
        "summary": "End a session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "session_id from POST /v1/sessions",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClosedSession"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/free-spins": {
      "post": {
        "tags": [
          "Promotions"
        ],
        "operationId": "grantFreeSpins",
        "summary": "Grant free spins; idempotent on reference",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFreeSpins"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The grant (the first one, on a repeated reference)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeSpins"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game provider refused the grant, or the games cannot be granted together",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "FREE_SPINS_REFUSED": {
                    "value": {
                      "error": "FREE_SPINS_REFUSED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "findFreeSpinsByReference",
        "summary": "Find a grant by your reference",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeSpins"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No grant under this reference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/free-spins/bet-options": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "getFreeSpinBetOptions",
        "summary": "Stakes the game provider accepts for a grant",
        "parameters": [
          {
            "name": "game_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted stakes, lowest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "game_id",
                    "currency",
                    "amounts"
                  ],
                  "properties": {
                    "game_id": {
                      "type": "string"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "amounts": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "A stake",
                        "pattern": "^-?\\d+(\\.\\d+)?$",
                        "examples": [
                          "1.50"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game provider does not take grants",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "FREE_SPINS_REFUSED": {
                    "value": {
                      "error": "FREE_SPINS_REFUSED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/free-spins/{id}": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "getFreeSpins",
        "summary": "Read a grant",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Grant id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeSpins"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such grant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/free-spins/{id}/cancel": {
      "post": {
        "tags": [
          "Promotions"
        ],
        "operationId": "cancelFreeSpins",
        "summary": "Withdraw a grant still playable; idempotent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Grant id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant with status cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeSpins"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such grant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game provider refused the cancellation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "FREE_SPINS_REFUSED": {
                    "value": {
                      "error": "FREE_SPINS_REFUSED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/bonus-campaigns": {
      "post": {
        "tags": [
          "Promotions"
        ],
        "operationId": "createBonusCampaign",
        "summary": "Place a bonus campaign for your players; idempotent on reference",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBonusCampaign"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The campaign (the first one, on a repeated reference)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BonusCampaign"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game provider refused the campaign, or a game is not in your window",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "BONUS_CAMPAIGN_REFUSED": {
                    "value": {
                      "error": "BONUS_CAMPAIGN_REFUSED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  },
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "findBonusCampaignByReference",
        "summary": "Find a campaign by your reference, or list the campaigns of one player",
        "description": "Exactly one of reference and player_id. With reference: that campaign. With player_id: the campaigns naming that player, newest first, at most 100.",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "description": "Your reference of the campaign; answers that one campaign",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player_id",
            "in": "query",
            "description": "Your player reference; answers the campaigns of that player",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "With reference: the campaign. With player_id: the campaigns of the player, an empty list when there are none",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BonusCampaign"
                    },
                    {
                      "$ref": "#/components/schemas/PlayerBonusCampaigns"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No campaign under this reference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Both reference and player_id, or neither",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/bonus-campaigns/options": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "getBonusCampaignOptions",
        "summary": "Campaign types and triggers the game provider of a game takes",
        "parameters": [
          {
            "name": "game_id",
            "in": "query",
            "required": true,
            "description": "id from GET /v1/games",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What a campaign on this game may use; empty lists when the game provider takes none",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BonusCampaignOptions"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The game is not in your window",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "GAME_UNAVAILABLE": {
                    "value": {
                      "error": "GAME_UNAVAILABLE",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "game_id is missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/bonus-campaigns/{id}": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "getBonusCampaign",
        "summary": "Read a campaign",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Campaign id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BonusCampaign"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such campaign",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/live-tables": {
      "get": {
        "tags": [
          "Catalogue"
        ],
        "operationId": "listLiveTables",
        "summary": "Live Casino tables on your games as they stand now, limits in one currency",
        "parameters": [
          {
            "name": "currency",
            "in": "query",
            "required": true,
            "description": "ISO code the limits are wanted in",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tables",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LiveTables"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No currency",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VALIDATION_FAILED": {
                    "value": {
                      "error": "VALIDATION_FAILED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/jackpots": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "listJackpots",
        "summary": "Progressive jackpots open on your games",
        "responses": {
          "200": {
            "description": "Open jackpots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Jackpots"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/jackpots/winners": {
      "get": {
        "tags": [
          "Promotions"
        ],
        "operationId": "listJackpotWinners",
        "summary": "Jackpots your players won lately, newest first",
        "responses": {
          "200": {
            "description": "Recent wins",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "winners"
                  ],
                  "properties": {
                    "winners": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/JackpotWinner"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/transactions/{id}": {
      "get": {
        "tags": [
          "Reconciliation"
        ],
        "operationId": "getTransaction",
        "summary": "One movement as our books hold it",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "transaction_id sent to your wallet",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The movement",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transaction"
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found or older than 180 days",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "NOT_FOUND": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rounds/{id}": {
      "get": {
        "tags": [
          "Reconciliation"
        ],
        "operationId": "getRound",
        "summary": "Every movement of one round, oldest first",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "round_id sent to your wallet",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The round",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "round_id",
                    "transactions"
                  ],
                  "properties": {
                    "round_id": {
                      "type": "string"
                    },
                    "transactions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UNAUTHORIZED": {
                    "value": {
                      "error": "UNAUTHORIZED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Signed, but from an address outside your allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADDRESS_NOT_ALLOWED": {
                    "value": {
                      "error": "ADDRESS_NOT_ALLOWED",
                      "message": "…",
                      "retryable": false,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the per-minute limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMITED": {
                    "value": {
                      "error": "RATE_LIMITED",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Whole seconds until the start of the next calendar minute, when the window restarts.",
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            }
          },
          "500": {
            "description": "An unexpected failure on our side; retry with the same ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A service behind the API did not answer; retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "UPSTREAM_UNAVAILABLE": {
                    "value": {
                      "error": "UPSTREAM_UNAVAILABLE",
                      "message": "…",
                      "retryable": true,
                      "fields": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "walletCall": {
      "post": {
        "operationId": "walletCall",
        "summary": "Wallet callback to your wallet_url (you implement this)",
        "description": "Signed with your current key, same headers and formula. Answer 2xx with a string balance. 4xx other than 408/429 is a refusal and is not retried. 5xx, 408, 429, no answer, or a 2xx whose body cannot be read is retried after 2 and 8 seconds, then a bet or win is rolled back.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WalletRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletResponse"
                }
              }
            }
          },
          "4XX": {
            "description": "Refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletError"
                }
              }
            }
          }
        }
      }
    },
    "invoice": {
      "post": {
        "operationId": "invoiceWebhook",
        "summary": "Invoice issued or voided (optional)",
        "description": "Posted when we send you an invoice. Signed like a wallet callback. 2xx accepts; 5xx, 429 or no answer is retried twice (2 s, 10 s, 15 s timeout each); any other 4xx is final.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWebhook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "KeyId": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Agg-Key-Id",
        "description": "Your key_id, ak_ + 24 hex characters"
      },
      "Timestamp": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Agg-Timestamp",
        "description": "Unix seconds, UTC, within 5 minutes of our clock"
      },
      "Signature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Agg-Signature",
        "description": "Lowercase hex HMAC_SHA256(secret, timestamp + \".\" + payload)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message",
          "retryable",
          "fields"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "UNAUTHORIZED",
              "VALIDATION_FAILED",
              "NOT_FOUND",
              "GAME_UNAVAILABLE",
              "GAME_RESTRICTED_IN_COUNTRY",
              "NO_DEMO",
              "PLAYER_BLOCKED",
              "FREE_SPINS_REFUSED",
              "BONUS_CAMPAIGN_REFUSED",
              "VALIDATION_RUN_REFUSED",
              "ADDRESS_NOT_ALLOWED",
              "RATE_LIMITED",
              "UPSTREAM_UNAVAILABLE"
            ]
          },
          "message": {
            "type": "string"
          },
          "retryable": {
            "type": "boolean",
            "description": "true: the outcome is unknown, repeat with the same ids"
          },
          "fields": {
            "type": "array",
            "description": "Filled on VALIDATION_FAILED only. field is the snake_case name of the body field or query parameter, or \"body\" when the body is not valid JSON.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "WalletValidationRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "description": "queued, running, passed, failed or error"
          },
          "passed": {
            "type": "integer"
          },
          "warned": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "description": "Why the run could not be carried out"
          },
          "cases": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "outcome": {
                  "type": "string",
                  "description": "pass, warn, fail or skipped"
                },
                "expected": {
                  "type": "string"
                },
                "actual": {
                  "type": "string"
                },
                "request": {
                  "type": "string",
                  "description": "The body sent"
                },
                "response_status": {
                  "type": "integer"
                },
                "response_body": {
                  "type": "string",
                  "description": "Cut to 2 KB"
                },
                "duration_ms": {
                  "type": "integer"
                },
                "docs_path": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Ping": {
        "type": "object",
        "required": [
          "operator_code",
          "key_id",
          "server_time"
        ],
        "properties": {
          "operator_code": {
            "type": "string"
          },
          "key_id": {
            "type": "string"
          },
          "server_time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GameType": {
        "type": "string",
        "enum": [
          "slot",
          "table",
          "live_dealer",
          "instant",
          "crash",
          "lottery",
          "virtual",
          "sportsbook",
          "skill",
          "other"
        ]
      },
      "Game": {
        "type": "object",
        "required": [
          "id",
          "provider_code",
          "code",
          "name",
          "type",
          "has_demo",
          "restricted_countries"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Catalogue id of the title (UUID). What POST /v1/sessions takes. Not what wallet callbacks carry as game_id.",
            "format": "uuid"
          },
          "provider_code": {
            "type": "string",
            "description": "The provider"
          },
          "code": {
            "type": "string",
            "description": "The provider game code, for display and support. Wallet callbacks may carry a different code for the same title."
          },
          "name": {
            "type": "string"
          },
          "studio": {
            "type": [
              "string",
              "null"
            ],
            "description": "Studio display name"
          },
          "studio_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Deprecated: repeats provider_code",
            "deprecated": true
          },
          "type": {
            "$ref": "#/components/schemas/GameType"
          },
          "rtp": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentage, e.g. 96.5"
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "images": {
            "type": "object",
            "properties": {
              "portrait": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "cover": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "has_demo": {
            "type": "boolean"
          },
          "restricted_countries": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "ISO 3166-1 alpha-2"
            }
          },
          "section_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pricing section the game belongs to, e.g. live"
          }
        }
      },
      "GamesPage": {
        "type": "object",
        "required": [
          "games"
        ],
        "properties": {
          "games": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Absent or null on the last page"
          }
        }
      },
      "Provider": {
        "type": "object",
        "required": [
          "provider_code",
          "provider_name",
          "game_count"
        ],
        "properties": {
          "provider_code": {
            "type": "string"
          },
          "provider_name": {
            "type": "string"
          },
          "studio_code": {
            "type": "string",
            "description": "Deprecated: repeats provider_code",
            "deprecated": true
          },
          "name": {
            "type": "string",
            "description": "Deprecated: repeats provider_name",
            "deprecated": true
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "game_count": {
            "type": "integer"
          },
          "images": {
            "type": "object",
            "properties": {
              "symbol": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "horizontal": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Rates": {
        "type": "object",
        "properties": {
          "revenue_base": {
            "type": "string",
            "enum": [
              "ggr",
              "ngr"
            ]
          },
          "market": {
            "type": "string",
            "enum": [
              "rest_of_world",
              "asia"
            ]
          },
          "regulated": {
            "type": "boolean"
          },
          "tier_mode": {
            "type": "string",
            "enum": [
              "whole",
              "progressive"
            ]
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider_code": {
                  "type": "string"
                },
                "provider_name": {
                  "type": "string"
                },
                "tiers": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "currency": {
                        "type": "string"
                      },
                      "ordinal": {
                        "type": "integer"
                      },
                      "from_amount": {
                        "type": "number",
                        "description": "Band floor, a JSON number"
                      },
                      "to_amount": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "Band ceiling; null on the top band"
                      }
                    }
                  }
                },
                "rates": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Rate"
                  }
                },
                "rates_by_market": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Rate"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "market": {
                            "type": "string"
                          }
                        }
                      }
                    ]
                  }
                },
                "sections": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "section_code": {
                        "type": "string"
                      },
                      "rates": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Rate"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Rate": {
        "type": "object",
        "properties": {
          "tier_ordinal": {
            "type": "integer"
          },
          "percent": {
            "type": "number",
            "description": "A percentage: 12.5 means 12.5%"
          }
        }
      },
      "CreateSession": {
        "type": "object",
        "required": [
          "player_id",
          "currency",
          "game_id"
        ],
        "properties": {
          "player_id": {
            "type": "string",
            "description": "Your reference, up to 120 characters",
            "maxLength": 120
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217, fixed for the session",
            "minLength": 3,
            "maxLength": 10
          },
          "game_id": {
            "type": "string",
            "description": "id from GET /v1/games"
          },
          "locale": {
            "type": "string",
            "description": "en, ru-RU, …"
          },
          "lobby_url": {
            "type": "string"
          },
          "ttl_minutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440,
            "default": 240
          },
          "player_country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2"
          },
          "player_ip": {
            "type": "string",
            "description": "The player's own address; the country is read off it",
            "maxLength": 45
          },
          "player_device": {
            "type": "string",
            "enum": [
              "desktop",
              "mobile"
            ]
          },
          "deposit_url": {
            "type": "string",
            "maxLength": 500
          },
          "player_name": {
            "type": "string",
            "description": "Nickname other players see in multiplayer games; never the real name",
            "maxLength": 32
          }
        }
      },
      "CreateDemo": {
        "type": "object",
        "properties": {
          "locale": {
            "type": "string"
          },
          "lobby_url": {
            "type": "string"
          },
          "player_country": {
            "type": "string"
          },
          "player_ip": {
            "type": "string"
          },
          "player_device": {
            "type": "string",
            "enum": [
              "desktop",
              "mobile"
            ]
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "session_id",
          "launch_url",
          "expires_at"
        ],
        "properties": {
          "session_id": {
            "type": "string"
          },
          "launch_url": {
            "type": "string",
            "description": "Open the game here; never build it yourself"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClosedSession": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "examples": [
              "closed"
            ]
          },
          "closed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateFreeSpins": {
        "type": "object",
        "required": [
          "player_id",
          "currency",
          "game_ids",
          "quantity",
          "bet_amount",
          "valid_until",
          "reference"
        ],
        "properties": {
          "player_id": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "game_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "description": "catalogue game ID from GET /v1/games, all of one provider"
            }
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000
          },
          "bet_amount": {
            "type": "string",
            "description": "Stake of one spin, from GET /v1/free-spins/bet-options",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "valid_until": {
            "type": "string",
            "description": "In the future, at most 90 days away",
            "format": "date-time"
          },
          "reference": {
            "type": "string",
            "description": "Your id for the grant; a repeat answers the first grant",
            "maxLength": 120
          }
        }
      },
      "FreeSpins": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "free_spins_id": {
            "type": "string",
            "description": "What your wallet sees on the bets and wins of the series"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "finished",
              "cancelled"
            ]
          },
          "player_id": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "game_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Game provider codes, as the series was placed"
            }
          },
          "quantity": {
            "type": "integer"
          },
          "bet_amount": {
            "type": "string",
            "description": "Stake of one spin",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "valid_until": {
            "type": "string",
            "format": "date-time"
          },
          "reference": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "win_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Total paid, for game providers that settle the series in one call",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          }
        }
      },
      "BonusCampaignType": {
        "type": "string",
        "enum": [
          "free_spins",
          "fortune_wheel",
          "treasure_chest",
          "golden_dragon",
          "buy_bonus"
        ]
      },
      "BonusTriggers": {
        "type": "object",
        "description": "At least one trigger. Each trigger that fires hands the bonus over once more.",
        "properties": {
          "first_game_launch": {
            "type": "boolean",
            "description": "The player opens one of the games for the first time in the campaign"
          },
          "bet_amount": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "amount"
              ],
              "properties": {
                "amount": {
                  "type": "string",
                  "description": "Stake to reach, positive",
                  "pattern": "^-?\\d+(\\.\\d+)?$",
                  "examples": [
                    "1.50"
                  ]
                },
                "single_bet": {
                  "type": "boolean",
                  "description": "true: one bet of at least amount; false: bets adding up to amount"
                }
              }
            }
          },
          "spin_count": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "min",
                "max"
              ],
              "properties": {
                "min": {
                  "type": "integer",
                  "minimum": 1
                },
                "max": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "At least min"
                }
              }
            }
          },
          "loss_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Net loss to reach, positive",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          }
        }
      },
      "BonusPrize": {
        "type": "object",
        "required": [
          "kind",
          "weight"
        ],
        "description": "money carries amount; free_spins carries spins and bet_amount. Chance = weight / sum of weights.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "money",
              "free_spins"
            ],
            "description": "free_spins only on a fortune_wheel"
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "money: the amount paid",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "spins": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "free_spins: how many"
          },
          "bet_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "free_spins: stake of one spin",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "weight": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CreateBonusCampaign": {
        "type": "object",
        "required": [
          "type",
          "name",
          "currency",
          "game_ids",
          "player_ids",
          "valid_until",
          "triggers",
          "reference"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/BonusCampaignType"
          },
          "name": {
            "type": "string",
            "description": "Shown to the player in the game",
            "maxLength": 100
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217; the game provider may take bonuses in some currencies only"
          },
          "game_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "description": "id from GET /v1/games, all of one game provider"
            }
          },
          "player_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "type": "string",
              "description": "Your player reference",
              "maxLength": 120
            }
          },
          "valid_from": {
            "type": "string",
            "description": "Optional; the campaign starts now when absent",
            "format": "date-time"
          },
          "valid_until": {
            "type": "string",
            "description": "In the future, after valid_from, at most 90 days after it",
            "format": "date-time"
          },
          "triggers": {
            "$ref": "#/components/schemas/BonusTriggers"
          },
          "spin_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "free_spins only, required there"
          },
          "bet_amount": {
            "type": "string",
            "description": "free_spins and buy_bonus only, required there: stake of one spin, or the price of the bought bonus",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "prizes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BonusPrize"
            },
            "description": "Drawn types only: exactly 16 for fortune_wheel and treasure_chest, 1 to 100 for golden_dragon; at least one weight above 0"
          },
          "min_cashout_multiplier": {
            "type": "string",
            "description": "free_spins on crash games: the least multiplier a spin cashes out at, 1.3 to below 100",
            "pattern": "^\\d+(\\.\\d+)?$",
            "examples": [
              "1.5"
            ]
          },
          "campaign_limit": {
            "type": "string",
            "description": "Optional: the total the campaign pays before it ends",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "reference": {
            "type": "string",
            "description": "Your id for the campaign; a repeat answers the first campaign",
            "maxLength": 120
          }
        }
      },
      "BonusCampaign": {
        "type": "object",
        "required": [
          "id",
          "campaign_id",
          "type",
          "status",
          "game_ids",
          "player_ids",
          "triggers",
          "prizes"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "campaign_id": {
            "type": "string",
            "description": "What your wallet sees as free_spins_id on the spins this campaign hands over"
          },
          "type": {
            "$ref": "#/components/schemas/BonusCampaignType"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "ended",
              "failed"
            ],
            "description": "ended: past valid_until; failed: the game provider refused it, see failure_reason"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "game_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "id from GET /v1/games"
            }
          },
          "player_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "valid_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "triggers": {
            "type": "object",
            "required": [
              "first_game_launch",
              "bet_amount",
              "spin_count"
            ],
            "properties": {
              "first_game_launch": {
                "type": "boolean"
              },
              "bet_amount": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "amount": {
                      "type": "string",
                      "description": "Stake to reach",
                      "pattern": "^-?\\d+(\\.\\d+)?$",
                      "examples": [
                        "1.50"
                      ]
                    },
                    "single_bet": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "spin_count": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "min": {
                      "type": "integer"
                    },
                    "max": {
                      "type": "integer"
                    }
                  }
                }
              },
              "loss_amount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Net loss to reach",
                "pattern": "^-?\\d+(\\.\\d+)?$",
                "examples": [
                  "1.50"
                ]
              }
            }
          },
          "spin_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "bet_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "free_spins: stake of one spin",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "prizes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BonusPrize"
            }
          },
          "min_cashout_multiplier": {
            "type": [
              "string",
              "null"
            ]
          },
          "campaign_limit": {
            "type": [
              "string",
              "null"
            ],
            "description": "The total the campaign pays before it ends",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the game provider refused it, when status is failed"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "PlayerBonusCampaigns": {
        "type": "object",
        "required": [
          "campaigns"
        ],
        "properties": {
          "campaigns": {
            "type": "array",
            "maxItems": 100,
            "description": "Campaigns naming the player, newest first; empty when there are none",
            "items": {
              "$ref": "#/components/schemas/BonusCampaign"
            }
          }
        }
      },
      "BonusCampaignOptions": {
        "type": "object",
        "required": [
          "game_id",
          "types",
          "triggers"
        ],
        "properties": {
          "game_id": {
            "type": "string"
          },
          "types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BonusCampaignType"
            }
          },
          "triggers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "first_game_launch",
                "bet_amount",
                "spin_count",
                "loss_amount"
              ]
            }
          }
        }
      },
      "LiveTables": {
        "type": "object",
        "required": [
          "currency",
          "pending",
          "tables"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "examples": [
              "EUR"
            ]
          },
          "pending": {
            "type": "boolean",
            "description": "The currency was asked for the first time just now; its tables arrive within seconds"
          },
          "tables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LiveTable"
            }
          }
        }
      },
      "LiveTable": {
        "type": "object",
        "required": [
          "game_id",
          "open"
        ],
        "properties": {
          "game_id": {
            "type": "string",
            "description": "id from GET /v1/games"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "open": {
            "type": "boolean",
            "description": "Whether the table takes bets now"
          },
          "new_table": {
            "type": "boolean"
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "The provider's current picture of the table"
          },
          "dealer": {
            "type": [
              "string",
              "null"
            ]
          },
          "min_bet": {
            "type": [
              "number",
              "null"
            ],
            "description": "Lowest bet in the currency asked for, a JSON number"
          },
          "max_bet": {
            "type": [
              "number",
              "null"
            ],
            "description": "Highest bet in the currency asked for, a JSON number"
          },
          "players": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Players at the table now"
          },
          "seats": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Seats at a seated table (blackjack); null elsewhere"
          },
          "seats_available": {
            "type": [
              "integer",
              "null"
            ]
          },
          "results": {
            "description": "Recent results in the provider's own shape, which differs by game type"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Jackpots": {
        "type": "object",
        "properties": {
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "jackpots": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "examples": [
                    "pragmatic:459"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "level": {
                  "type": "string",
                  "enum": [
                    "global",
                    "network",
                    "operator",
                    "brand"
                  ]
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "paused"
                  ]
                },
                "currency": {
                  "type": "string",
                  "examples": [
                    "USD"
                  ]
                },
                "tiers": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "tier_id": {
                        "type": "string"
                      },
                      "tier": {
                        "type": "integer"
                      },
                      "amount": {
                        "type": "number",
                        "description": "Current amount, a JSON number (not a string)"
                      }
                    }
                  }
                },
                "games": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "game_id": {
                        "type": "string",
                        "description": "id from GET /v1/games"
                      },
                      "name": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "JackpotWinner": {
        "type": "object",
        "properties": {
          "jackpot_tier_id": {
            "type": "string"
          },
          "player_id": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "Amount won, a JSON number (not a string)"
          },
          "currency": {
            "type": "string"
          },
          "won_at": {
            "type": "string",
            "format": "date-time"
          },
          "win_type": {
            "type": "string",
            "enum": [
              "progressive",
              "community",
              "fixed"
            ]
          },
          "round_id": {
            "type": "string",
            "description": "The provider's round id"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "transaction_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "bet",
              "win",
              "rollback"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "refused",
              "failed",
              "cancelled",
              "pending"
            ]
          },
          "round_id": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "player_id": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "Amount of the movement",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "reference_transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "For a rollback: what it undid"
          },
          "operator_transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id your wallet answered with"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WalletRequest": {
        "type": "object",
        "required": [
          "action",
          "transaction_id",
          "session_id",
          "round_id",
          "player_id",
          "currency"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "getbalance",
              "bet",
              "win",
              "rollback"
            ]
          },
          "transaction_id": {
            "type": "string",
            "description": "Idempotency key, \"<provider>:<id>\". Empty string on getbalance"
          },
          "reference_transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "rollback only: the transaction being undone"
          },
          "session_id": {
            "type": "string"
          },
          "round_id": {
            "type": "string",
            "description": "Empty string on getbalance"
          },
          "player_id": {
            "type": "string"
          },
          "game_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Game provider code of the game the round is played on; null on getbalance. Not the catalogue id"
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "In the currency's own precision; null on getbalance",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "free_spins_id": {
            "type": "string",
            "description": "Present only on bets and wins of a free-spin series; for spins a bonus campaign hands over it is the campaign_id"
          }
        }
      },
      "WalletResponse": {
        "type": "object",
        "required": [
          "balance"
        ],
        "properties": {
          "balance": {
            "type": "string",
            "description": "Balance after the movement. Must be a string: a number is read as an unreadable answer",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          },
          "transaction_id": {
            "type": "string",
            "description": "Your own id for the movement"
          },
          "bonus_amount": {
            "type": "string",
            "description": "bet and win only: the part that was bonus money",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "examples": [
              "1.50"
            ]
          }
        }
      },
      "WalletError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "INSUFFICIENT_FUNDS, BET_LIMIT, PLAYER_BLOCKED, …"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "InvoiceWebhook": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "invoice.issued",
              "invoice.voided"
            ]
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "invoice": {
            "type": "object",
            "description": "Figures (revenue, bonus, gross, rate_percent, amount, adjustment_amount, tax_percent, tax_amount, total, ggr, band amounts) are JSON numbers.",
            "properties": {
              "id": {
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "period_from": {
                "type": "string",
                "format": "date-time"
              },
              "period_to": {
                "type": "string",
                "description": "Exclusive",
                "format": "date-time"
              },
              "issued_at": {
                "type": "string",
                "format": "date-time"
              },
              "due_at": {
                "type": "string",
                "format": "date-time"
              },
              "revenue_base": {
                "type": "string",
                "enum": [
                  "ggr",
                  "ngr"
                ]
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "revenue": {
                      "type": "number",
                      "description": "JSON number"
                    },
                    "total": {
                      "type": "number",
                      "description": "JSON number"
                    }
                  }
                }
              },
              "void_reason": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "document": {
            "type": "object",
            "properties": {
              "file_name": {
                "type": "string"
              },
              "content_type": {
                "type": "string"
              },
              "content_base64": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}