> For the complete documentation index, see [llms.txt](https://docs.hub88.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hub88.io/developer-docs/operator-api-reference/tournaments-api.md).

# Tournaments API

## List your tournaments

> Returns the tournaments assigned to your operator.\
> \
> If you don't send a \`status\`, you get every active and scheduled tournament\
> in a single response. To browse finished tournaments instead, send\
> \`status: "ended"\`. Those come back most recently ended first, up to 100 at a\
> time, and this is the only case where the list is paged.\
> \
> While there are more ended tournaments to fetch, the response includes a\
> \`next\_cursor\`. Send it back as \`cursor\`, with the same \`status\` and \`user\` as\
> before, to get the next page. Keep going until a response arrives without a\
> \`next\_cursor\`. A page with fewer than 100 tournaments doesn't on its own mean\
> you have reached the end.<br>

````json
{"openapi":"3.1.0","info":{"title":"Hub88 Tournaments API","version":"1.0.0"},"tags":[{"name":"Operator","description":"Endpoints your backend calls directly, authenticated by a signature over the request body."}],"servers":[{"url":"https://{host}","variables":{"host":{"default":"api.hub88.io","description":"The API host Hub88 gives you for your environment."}}}],"security":[{"OperatorSignature":[]}],"components":{"securitySchemes":{"OperatorSignature":{"type":"apiKey","in":"header","name":"X-Hub88-Signature","description":"Every request must carry an `X-Hub88-Signature` header. To build it, sign\nthe exact bytes of the request body with your operator private key, using\nRSA with SHA-256, then Base64-encode the result. Hub88 checks the signature\nagainst the public key registered for your operator.\n\nThe body must include your `operator_id`. Send exactly the bytes you signed.\nIf your HTTP client re-serializes the JSON or changes its whitespace after\nyou sign it, the signature no longer matches and the request is refused.\n\n```bash\nBODY='{\"operator_id\":10}'\nSIG=$(printf '%s' \"$BODY\" | openssl dgst -sha256 -sign operator-private.pem | openssl base64 -A)\ncurl -X POST https://$HOST/operator/generic/v2/tournaments/list \\\n  -H 'content-type: application/json' \\\n  -H \"x-hub88-signature: $SIG\" \\\n  -d \"$BODY\"\n```\n"}},"schemas":{"ListTournamentsRequest":{"type":"object","required":["operator_id"],"properties":{"operator_id":{"$ref":"#/components/schemas/OperatorId"},"status":{"type":"string","enum":["active","scheduled","ended"],"description":"The state of the tournaments to return. The value is case-sensitive, so\n`Ended` is rejected.\n\nDefault: active and scheduled tournaments together.\n"},"user":{"type":"string","minLength":1,"description":"The identifier you use for one of your players. If you set it, the list contains only tournaments that this player has opted into."},"cursor":{"allOf":[{"$ref":"#/components/schemas/Cursor"}],"description":"The `next_cursor` from the previous page. It's only accepted together with `status: ended`, because that is the only paged list."}}},"OperatorId":{"type":"integer","description":"The Hub88 ID of your operator. It must belong to the operator whose key signed the request."},"Cursor":{"type":"string","minLength":1,"description":"A paging token taken from the `next_cursor` of a previous response. Treat it\nas opaque and send it back exactly as you received it.\n"},"TournamentListing":{"type":"object","required":["tournaments"],"properties":{"tournaments":{"type":"array","items":{"$ref":"#/components/schemas/Tournament"}},"next_cursor":{"allOf":[{"$ref":"#/components/schemas/Cursor"}],"description":"The cursor for the next page. It's included only when there is another page to fetch."}}},"Tournament":{"type":"object","required":["tournament_uuid","name","tournament","eligibility","terms_and_conditions"],"properties":{"tournament_uuid":{"type":"string","format":"uuid"},"name":{"type":"string"},"tournament":{"$ref":"#/components/schemas/TournamentDetails"},"eligibility":{"$ref":"#/components/schemas/Eligibility"},"terms_and_conditions":{"type":["string","null"]}}},"TournamentDetails":{"type":"object","required":["state","scoring_template","score_kind","event_time","prize_pool"],"properties":{"state":{"type":"string","enum":["active","scheduled","ended"],"description":"The current state of the tournament. `ended` covers every finished tournament, whether or not its prizes have been paid out yet."},"scoring_template":{"type":"string","enum":["biggest_single_win","win_multiplier","sum_multiplier","points_per_round"],"description":"The rule used to work out each player's score."},"score_kind":{"type":"string","enum":["money","points"],"description":"The kind of score players earn. `money` means it is an amount in `score_currency_code`, and `points` means it is a number of points."},"score_currency_code":{"type":"string","description":"The currency that money scores are expressed in. It is only included\nwhen `score_kind` is `money`, so a points tournament has no such field.\n"},"event_time":{"$ref":"#/components/schemas/EventTime"},"prize_pool":{"description":"The prizes on offer, or `null` if the prize pool can't be shown right now. The rest of the tournament is still returned.","oneOf":[{"$ref":"#/components/schemas/PrizePool"},{"type":"null"}]}}},"EventTime":{"type":"object","description":"The start and end times of the tournament. This object is always present, but either time can be `null`.","required":["start_time","end_time"],"properties":{"start_time":{"type":["string","null"],"format":"date-time","description":"The time the tournament starts, in UTC, as ISO 8601 with microseconds."},"end_time":{"type":["string","null"],"format":"date-time","description":"The time the tournament ends, in UTC, as ISO 8601 with microseconds."}}},"PrizePool":{"type":"object","required":["total","currency_code","tiers"],"properties":{"total":{"$ref":"#/components/schemas/Amount"},"currency_code":{"type":"string","description":"The currency of `total` and of every tier's `amount`, as an ISO 4217 code."},"tiers":{"type":"array","items":{"$ref":"#/components/schemas/PrizeTier"}}}},"Amount":{"type":"string","description":"A decimal amount sent as a string, at the currency's own precision.","pattern":"^-?[0-9]+(\\.[0-9]+)?$"},"PrizeTier":{"type":"object","required":["rank","amount"],"properties":{"rank":{"type":"integer","minimum":1},"amount":{"$ref":"#/components/schemas/Amount"}}},"Eligibility":{"type":"object","description":"The rules for which players, games and bets count toward the tournament.\nThe four code lists are allow-lists, so an empty list means nothing in that\ncategory qualifies, not that everything does.\n","required":["country_codes","currency_codes","product_codes","game_codes","max_spin","bet_criteria"],"properties":{"country_codes":{"type":"array","items":{"type":"string"},"description":"The countries players can take part from, as ISO 3166-1 alpha-2 codes."},"currency_codes":{"type":"array","items":{"type":"string"},"description":"The wallet currencies players can take part with, as ISO 4217 codes."},"product_codes":{"type":"array","items":{"type":"string"},"description":"The products whose games count toward the tournament."},"game_codes":{"type":"array","items":{"type":"string"},"description":"The games that count toward the tournament."},"max_spin":{"type":["integer","null"],"minimum":1,"description":"The tournament's spin cap, or `null` if there is none."},"bet_criteria":{"$ref":"#/components/schemas/BetCriteria"}}},"BetCriteria":{"type":"object","description":"The limits a bet has to meet to count toward the tournament. A limit set to\n`null` doesn't apply. All three amounts are in `currency_code`, a single\ncurrency that is not related to the `currency_codes` list in `eligibility`.\n","required":["currency_code","min_bet_amount","max_bet_amount","max_win_amount"],"properties":{"currency_code":{"type":["string","null"]},"min_bet_amount":{"oneOf":[{"$ref":"#/components/schemas/Amount"},{"type":"null"}]},"max_bet_amount":{"oneOf":[{"$ref":"#/components/schemas/Amount"},{"type":"null"}]},"max_win_amount":{"oneOf":[{"$ref":"#/components/schemas/Amount"},{"type":"null"}]}}},"ListInvalidRequest":{"type":"object","required":["error","reason"],"properties":{"error":{"type":"string","const":"Invalid request"},"reason":{"type":"string","enum":["unknown_status","invalid_user","cursor_not_supported","invalid_cursor","cursor_query_mismatch"],"description":"- `unknown_status`: `status` isn't one of `active`, `scheduled` or `ended`.\n- `invalid_user`: `user` is empty or isn't a string.\n- `cursor_not_supported`: you sent a `cursor` without `status: ended`.\n- `invalid_cursor`: the cursor can't be read. Send it back exactly as you\n  received it.\n- `cursor_query_mismatch`: the cursor came from a request with a different\n  `status` or `user`. Repeat the original filters when you page.\n"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","enum":["Authentication failed","Service unavailable"]}}}},"responses":{"OperatorUnauthenticated":{"description":"The request couldn't be authenticated. This happens when the signature\nheader is missing, when the signature doesn't match, or when the body is\nempty. The response is the same in every case, so start by checking your\nsigning code and the key you sign with.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"We couldn't answer your request right now. This is temporary. It doesn't\nmean your credentials are wrong or that you have no data, so retry after a\nshort wait.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/operator/generic/v2/tournaments/list":{"post":{"tags":["Operator"],"operationId":"listTournaments","summary":"List your tournaments","description":"Returns the tournaments assigned to your operator.\n\nIf you don't send a `status`, you get every active and scheduled tournament\nin a single response. To browse finished tournaments instead, send\n`status: \"ended\"`. Those come back most recently ended first, up to 100 at a\ntime, and this is the only case where the list is paged.\n\nWhile there are more ended tournaments to fetch, the response includes a\n`next_cursor`. Send it back as `cursor`, with the same `status` and `user` as\nbefore, to get the next page. Keep going until a response arrives without a\n`next_cursor`. A page with fewer than 100 tournaments doesn't on its own mean\nyou have reached the end.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTournamentsRequest"}}}},"responses":{"200":{"description":"The tournaments that match your request. If nothing matches, you get an\nempty `tournaments` list. That is a normal answer, not an error.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TournamentListing"}}}},"400":{"description":"The request was signed correctly, but one of its fields can't be used. The `reason` tells you which one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListInvalidRequest"}}}},"401":{"$ref":"#/components/responses/OperatorUnauthenticated"},"503":{"$ref":"#/components/responses/Unavailable"}}}}}}
````

## Get a tournament's rankings

> Returns one tournament's leaderboard, filtered down to your own players, up\
> to 100 rows at a time.\
> \
> Each row keeps the \`rank\` and \`position\` the player holds on the full\
> leaderboard, which also includes other operators' players. That means the\
> numbers you see can skip values. For example, your first player might be at\
> position 2 and your next one at position 7.\
> \
> To page through a long leaderboard, send each response's \`next\_cursor\` back\
> as \`cursor\`. A cursor only works for the version of the leaderboard it came\
> from. If the leaderboard is updated while you are paging, your next request\
> fails with \`400\` and the reason \`stale\_cursor\`. When that happens, start\
> again from the first page, without a cursor.<br>

````json
{"openapi":"3.1.0","info":{"title":"Hub88 Tournaments API","version":"1.0.0"},"tags":[{"name":"Operator","description":"Endpoints your backend calls directly, authenticated by a signature over the request body."}],"servers":[{"url":"https://{host}","variables":{"host":{"default":"api.hub88.io","description":"The API host Hub88 gives you for your environment."}}}],"security":[{"OperatorSignature":[]}],"components":{"securitySchemes":{"OperatorSignature":{"type":"apiKey","in":"header","name":"X-Hub88-Signature","description":"Every request must carry an `X-Hub88-Signature` header. To build it, sign\nthe exact bytes of the request body with your operator private key, using\nRSA with SHA-256, then Base64-encode the result. Hub88 checks the signature\nagainst the public key registered for your operator.\n\nThe body must include your `operator_id`. Send exactly the bytes you signed.\nIf your HTTP client re-serializes the JSON or changes its whitespace after\nyou sign it, the signature no longer matches and the request is refused.\n\n```bash\nBODY='{\"operator_id\":10}'\nSIG=$(printf '%s' \"$BODY\" | openssl dgst -sha256 -sign operator-private.pem | openssl base64 -A)\ncurl -X POST https://$HOST/operator/generic/v2/tournaments/list \\\n  -H 'content-type: application/json' \\\n  -H \"x-hub88-signature: $SIG\" \\\n  -d \"$BODY\"\n```\n"}},"schemas":{"RankingsRequest":{"type":"object","required":["operator_id","tournament_uuid"],"properties":{"operator_id":{"$ref":"#/components/schemas/OperatorId"},"tournament_uuid":{"type":"string","format":"uuid"},"cursor":{"$ref":"#/components/schemas/Cursor"}}},"OperatorId":{"type":"integer","description":"The Hub88 ID of your operator. It must belong to the operator whose key signed the request."},"Cursor":{"type":"string","minLength":1,"description":"A paging token taken from the `next_cursor` of a previous response. Treat it\nas opaque and send it back exactly as you received it.\n"},"RankingsPage":{"type":"object","required":["tournament_uuid","own_participant_count","rankings"],"properties":{"tournament_uuid":{"type":"string","format":"uuid"},"own_participant_count":{"type":"integer","minimum":0,"description":"The total number of your players on this leaderboard, across every page, not only this one."},"rankings":{"type":"array","items":{"$ref":"#/components/schemas/RankingEntry"}},"next_cursor":{"allOf":[{"$ref":"#/components/schemas/Cursor"}],"description":"The cursor for the next page. It's included only when there is another page to fetch."}}},"RankingEntry":{"type":"object","required":["position","rank","operator_id","operator_user","score"],"properties":{"position":{"type":"integer","minimum":1,"description":"The row's place on the full leaderboard, counting every operator's players."},"rank":{"type":"integer","minimum":1,"description":"The player's standing on the full leaderboard, counting every operator's players."},"operator_id":{"$ref":"#/components/schemas/OperatorId"},"operator_user":{"type":"string","description":"The identifier you use for the player."},"score":{"$ref":"#/components/schemas/Amount"}}},"Amount":{"type":"string","description":"A decimal amount sent as a string, at the currency's own precision.","pattern":"^-?[0-9]+(\\.[0-9]+)?$"},"RankingsInvalidRequest":{"type":"object","required":["error","reason"],"properties":{"error":{"type":"string","const":"Invalid request"},"reason":{"type":"string","enum":["missing_tournament_uuid","invalid_tournament_uuid","invalid_cursor","cursor_tournament_mismatch","stale_cursor"],"description":"- `missing_tournament_uuid`: you didn't send a `tournament_uuid`.\n- `invalid_tournament_uuid`: `tournament_uuid` isn't a valid UUID.\n- `invalid_cursor`: the cursor can't be read. Send it back exactly as you\n  received it.\n- `cursor_tournament_mismatch`: the cursor came from a different\n  tournament.\n- `stale_cursor`: the leaderboard was updated after the cursor was issued.\n  Start again from the first page, without a cursor.\n"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","enum":["Authentication failed","Service unavailable"]}}},"NotFound":{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"Not found"}}}},"responses":{"OperatorUnauthenticated":{"description":"The request couldn't be authenticated. This happens when the signature\nheader is missing, when the signature doesn't match, or when the body is\nempty. The response is the same in every case, so start by checking your\nsigning code and the key you sign with.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"We couldn't answer your request right now. This is temporary. It doesn't\nmean your credentials are wrong or that you have no data, so retry after a\nshort wait.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/operator/generic/v2/tournaments/rankings":{"post":{"tags":["Operator"],"operationId":"getTournamentRankings","summary":"Get a tournament's rankings","description":"Returns one tournament's leaderboard, filtered down to your own players, up\nto 100 rows at a time.\n\nEach row keeps the `rank` and `position` the player holds on the full\nleaderboard, which also includes other operators' players. That means the\nnumbers you see can skip values. For example, your first player might be at\nposition 2 and your next one at position 7.\n\nTo page through a long leaderboard, send each response's `next_cursor` back\nas `cursor`. A cursor only works for the version of the leaderboard it came\nfrom. If the leaderboard is updated while you are paging, your next request\nfails with `400` and the reason `stale_cursor`. When that happens, start\nagain from the first page, without a cursor.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankingsRequest"}}}},"responses":{"200":{"description":"One page of the leaderboard. If the tournament doesn't have a leaderboard\nyet, you get an empty `rankings` list and an `own_participant_count` of `0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankingsPage"}}}},"400":{"description":"The request was signed correctly, but one of its fields can't be used. The `reason` tells you which one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankingsInvalidRequest"}}}},"401":{"$ref":"#/components/responses/OperatorUnauthenticated"},"404":{"description":"The tournament isn't assigned to your operator. You get the same answer\nfor a tournament that doesn't exist, so this response never reveals\nwhether a tournament belongs to another operator.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFound"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}}}}
````


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.hub88.io/developer-docs/operator-api-reference/tournaments-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
