Skip to content

Authorize a paid 1v1 challenge or tournament for one signed transaction

POST
/api/tournaments/challenges/authorize
curl --request POST \
--url https://foxleague.ai/api/tournaments/challenges/authorize \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "challenge_deadline_minutes": 1, "deadline": "2026-04-15T12:00:00Z", "description": "example", "developer_mode": true, "entry_fee_foxsy": 1, "expected_platform_fee_percent": 1, "format": "example", "has_third_place_match": true, "home_and_away": true, "max_teams": 1, "min_teams": 1, "organizer_fee_tx_hash": "example", "organizer_only": true, "ovr_max": 1, "ovr_min": 1, "round_interval_minutes": 1, "snapshot_at": "2026-04-15T12:00:00Z", "sponsor_prize_foxsy": 1, "starts_at": "2026-04-15T12:00:00Z", "target_user_id": 1, "tier": "example", "title": "example", "whitelist_user_ids": [ 1 ] }'

Asks the server to authorize a paid 1v1 challenge or a paid standard tournament you play in, so that you create it and pay its entry fee in ONE transaction of yours, createAndJoin, instead of a server create followed by your joinTournament. Send the same body as POST /api/tournaments: a Silver or Gold challenge with max_teams: 2; or a Silver or Gold tournament of more teams that you play in (not organizer_only). The server runs every check the normal create runs, writes it in a hidden state that only you can see and answers the signed terms: the exact fee, the seat cap (max_entries: 2 for a challenge, the tournament’s max_teams otherwise), the allowed wallets in order (none for a public tournament; you and your invited teams for a private one), the expiry in chain time, the server’s signature and the gas limit to send. Build the transaction from them exactly as answered and pay; the contract refuses any other amount, cap, wallet, order or key. Then call POST /api/tournaments/challenges/{id}/confirm.

A tournament keeps the registration deadline and the start you chose. Its booking on the calendar is held for it from this call until it opens or its window closes unpaid: other players see the slot busy without its name. A tournament that does not open (its window closes unpaid or it is refused once paid) returns your organizer payment in full the first time at its time slot. Your next tournament in an overlapping slot then has no free cancellation (no_free_cancel): if it does not open either, its calendar deposit is kept. A challenge’s deadline is counted from the moment it opens.

Nothing is created on chain by this call. Calling it again with the same body before the expiry answers the same authorization with the seconds left. A different body while one of yours (a challenge or a tournament) can still be paid is refused until its window closes. When a previous authorization of yours turns out paid, the answer has its state (open) and its tournament_id: open it, there is nothing to sign.

A challenge is offered only while GET /api/config answers tournament_defaults.challenge_create_and_join: true, a tournament only while it answers tournament_defaults.standard_create_and_join: true.

Who: a signed-in player with a team.

Refusals:

  • The one-signature challenge flow is not available right now. Create the challenge the usual way.: the flow is switched off for challenges or the contract does not offer it yet.
  • The one-signature flow is not available for tournaments right now. Create the tournament the usual way.: the same for a tournament.
  • Chain is temporarily unavailable. Please retry shortly.: the server cannot reach the chain right now.
  • The chain clock could not be read. Please retry in a moment.: the expiry is chain time and the chain time could not be read.
  • This wallet cannot create a challenge. / This wallet cannot create a tournament.: an operator or contract wallet.
  • Challenges are paused on chain right now. Nothing was charged; please try again later. / Paid tournaments are paused on chain right now. Nothing was charged; please try again later.: the tournaments contract is paused.
  • The one-signature flow creates paid 1v1 challenges only (Silver or Gold, two seats). Create other tournaments the usual way.: a challenge body (max_teams: 2) that is not paid.
  • Developer challenges are created the usual way. / Developer tournaments are created the usual way.: the body has developer_mode: true. It is created with POST /api/tournaments and joined with POST /api/tournaments/{id}/join, which runs the developer checks.
  • Champions FoxLeague tournaments are created the usual way.: league_knockout or a snapshot_at. An admin creates it; its seats go by invitation.
  • The one-signature flow creates a paid tournament you play in. Create an organizer-only tournament the usual way.: organizer_only is set: you take no seat, so there is nothing to pay.
  • The one-signature flow creates paid tournaments only (Silver or Gold with an entry fee). Create free tournaments the usual way.: a tournament without an entry fee.
  • A prize from FoxLeague is deposited on its own. Create this tournament the usual way.: the body has sponsor_prize_foxsy.
  • Couldn't verify your squad's match fitness right now. Please try again in a moment. / Couldn't verify your squad's transfer cooldowns right now. Please try again in a moment.: a gate of your seat could not be read (a challenge’s creator is asked the match-fit gate and the transfer cooldown, a tournament’s the cooldown only); nothing was written.
  • The registration deadline is too close to pay in one step. Choose a deadline at least <minutes> minutes from now.: a tournament whose deadline falls inside the deadline floor: the authorization window, the chain’s finality margin and two intervals of the check that opens a paid tournament whose confirmation never arrived (about 13 minutes by default; GET /api/config answers the exact seconds as tournament_defaults.standard_create_and_join_min_deadline_seconds), so a payment could land after registration closed.
  • You already started a challenge for <fee> FOXSY against <team>. Finish it in the wallet or wait <N>s to start a different one. / You already started a tournament for <fee> FOXSY. Finish it in the wallet or wait <N>s to start a different one.: a previous authorization of yours can still be paid. One authorization at a time, a challenge or a tournament.
  • You already started a challenge that can still be paid. Finish it in the wallet or wait for its window to close before starting another. / You already started a tournament that can still be paid. Finish it in the wallet or wait for its window to close before starting another.: two authorizes of yours raced; one was written.
  • You already have an active 1v1. Finish it or cancel it first.: you are in another live challenge (an authorized one counts until its window closes).
  • You already have an active tournament you are playing in. Complete or cancel it before creating another.: you created another active tournament you play in (an authorized one counts until its window closes).
  • Time slot is held by a tournament that is being created. Choose a different time.: the tournament’s booking overlaps one held for a tournament whose creator has not paid yet. Your own tournament whose payment window has closed holds nothing against you: the one you authorize at its time slot replaces it.
  • Every refusal of POST /api/tournaments for a 1v1 challenge or a tournament you play in, in the same words.
  • Could not authorize the challenge right now. Please retry. / Could not authorize the tournament right now. Please retry.: the terms could not be signed or written; nothing was written.
  • Signing service unavailable. Please retry.: the server could not sign; nothing was written.
Media type application/json

The settings of a new tournament or 1v1 challenge.

object
challenge_deadline_minutes

1v1 challenge only: minutes the challenge stays open, 30, 60, 120 or 240 (default 60). Kickoff is five minutes after it closes; when an opponent accepts, registration closes at once and the match is scheduled shortly after.

integer | null format: int32
deadline
required

Registration closes at this time (RFC 3339); it must be in the future. Required but ignored for a 1v1 challenge.

string format: date-time
description

Optional text, at most 1000 characters.

string | null
developer_mode

Only teams with a verified developer binary may join. Available on every format except Champions FoxLeague.

boolean | null
entry_fee_foxsy

Entry fee in whole FOXSY. Bronze: 0 or absent until the competition levels go live; from then a Bronze needs a fee inside the Bronze band, a developer one stays free. Silver and Gold need a fee inside the tier’s range; the refusal names the range.

integer | null format: int64
expected_platform_fee_percent

Paid private tournament only: the platform fee, in percent, your page showed (platform_fee_percent of private_tournament_prize_split in GET /api/config). A different percent is refused before anything is written: read the config again. Omitted: nothing is compared.

integer | null format: int64
format
required

knockout, groups_knockout, round_robin, swiss or league_knockout (Champions FoxLeague: created by an admin only, while the admin has switched the format on). A 1v1 challenge is knockout.

string
has_third_place_match

Adds a match for third place between the losing semifinalists. Default false.

boolean | null
home_and_away

round_robin only: every pair of teams meets twice. Ignored for other formats.

boolean | null
max_teams
required

Seats. knockout: 2, 4, 8, 16 or 32 (2 makes a 1v1 challenge). groups_knockout: 8, 16, 32 or 64. round_robin: 3 to 20. swiss: 3 to 32. league_knockout: 36.

integer format: int32
min_teams
required

Teams needed to start; with fewer at the deadline the tournament is cancelled. At least 2 for knockout, 6 for groups_knockout, 36 for league_knockout and 3 for round_robin and swiss; at most max_teams. Equal to max_teams for knockout and league_knockout.

integer format: int32
organizer_fee_tx_hash

The hash of your shopPay payment of the deposit and, on a private tournament, the fee: 64 hexadecimal characters. Needed once the organizer fees apply and the tournament books the calendar; ask POST /api/tournaments/organizer-fee/quote for the amount first.

string | null
organizer_only

Standard tournament only: you run the tournament without a seat and cannot join it later.

boolean | null
ovr_max

Optional highest best XI OVR a joining team may have: a whole number from 1 to 100, not below ovr_min.

integer | null format: int32
ovr_min

Optional lowest best XI OVR a joining team may have: a whole number from 1 to 100, not above ovr_max. Not for Champions FoxLeague or a developer tournament. If you take a seat, your own best XI OVR must be inside the range.

integer | null format: int32
round_interval_minutes

Minutes between one match’s kickoff and the next: the tournament plays its matches one after another. Default 30; a lower value counts as 30; at most 1440.

integer | null format: int32
snapshot_at

Champions FoxLeague only (required there): when the leaderboard is frozen and the first invitation wave goes out. In the future and at least 4 days (an admin setting) before deadline. Refused for every other format.

string | null format: date-time
sponsor_prize_foxsy

Champions FoxLeague only, optional: a prize from FoxLeague in whole FOXSY, at least 1, paid by the tournaments contract to the top 24 by how far they got: 4 shares to each quarter-finalist, 2 to each round-of-16 loser, 1 to each play-off loser (56 shares, 57.1, 28.6 and 14.3 percent). Only on a Silver or Gold tournament and only when your wallet is an approved sponsor on the contract. Your wallet deposits it after the create, before the snapshot.

integer | null format: int64
starts_at
required

Kickoff of the first match (RFC 3339): more than one hour from now, more than five minutes after deadline and at most 90 days ahead. Required but ignored for a 1v1 challenge.

string format: date-time
target_user_id

1v1 challenge only: the user id of the only opponent who may accept. Not your own id.

integer | null format: int32
tier
required

bronze, silver or gold. Bronze is free until the competition levels go live; from then a player’s Bronze costs the Bronze band (tournament_fee_bounds.bronze_min to bronze_max). Silver and Gold are paid in FOXSY. A 1v1 challenge is Silver or Gold; Bronze only for a developer challenge.

string
title
required

Name of the tournament, 1 to 100 characters, not blank.

string
whitelist_user_ids

Standard tournament only, never Champions FoxLeague (its invitations come from the leaderboard): user ids of the teams you invite. A non-empty list makes the tournament private. Your own id and repeated ids are dropped. The list fits the seats: at most max_teams - 1 ids when you play, max_teams with organizer_only.

Array<integer> | null
Example generated
{
"challenge_deadline_minutes": 1,
"deadline": "2026-04-15T12:00:00Z",
"description": "example",
"developer_mode": true,
"entry_fee_foxsy": 1,
"expected_platform_fee_percent": 1,
"format": "example",
"has_third_place_match": true,
"home_and_away": true,
"max_teams": 1,
"min_teams": 1,
"organizer_fee_tx_hash": "example",
"organizer_only": true,
"ovr_max": 1,
"ovr_min": 1,
"round_interval_minutes": 1,
"snapshot_at": "2026-04-15T12:00:00Z",
"sponsor_prize_foxsy": 1,
"starts_at": "2026-04-15T12:00:00Z",
"target_user_id": 1,
"tier": "example",
"title": "example",
"whitelist_user_ids": [
1
]
}

Success or a refusal: read success (the refusals are in the description).

Media type application/json
object
code

A machine-readable reason next to error, set only where the caller must act differently. tx_pending: the payment was not refused but could not be verified yet; keep the transaction hash and confirm again with the same hash, never pay twice.

string | null
data

The answer. Present when success is true.

object
allowed_wallets
required

The allowed wallets, in the exact order to send them: you first, then the challenged player for a targeted challenge. For a tournament: none when it is public, you and your invited teams (in account order) when it is private.

Array<string>
entry_fee_atomic
required

The exact amount the transaction must carry, in FOXSY atomic units, as a decimal string.

string
entry_fee_foxsy
required

The entry fee in whole FOXSY.

integer format: int64
expires_in_seconds
required

Seconds left on expiry at the time of this answer, by chain time. In a pending refusal: the seconds to wait before a different challenge or tournament can start.

integer format: int64
expiry
required

Chain time in seconds up to and including which the contract accepts the signature.

integer format: int64
foxsy_token_id
required

The FOXSY token to pay with.

string
gas_limit
required

The gas limit to send the transaction with. One figure for a challenge; for a tournament it grows with the allowed wallets.

integer format: int64
max_entries
required

The seat cap to send: 2 for a challenge, the tournament’s max_teams for a tournament.

integer format: int64
sc_address
required

The tournaments contract to send the transaction to.

string
signature_hex
required

The server’s 64-byte signature of the terms, as hexadecimal.

string
state
required

authorized: sign and pay with the fields below. pending (inside a refusal with code challenge_authorization_pending): a previous authorization of yours can still land; tournament_id is that challenge or tournament and expires_in_seconds the seconds to wait before a different one can start. Any other value is the status of a previous challenge or tournament of yours that turned out paid (open): nothing to sign, go to tournament_id.

string
tournament_id
required

The id of the challenge or tournament: the first argument of the transaction and the id to confirm.

integer format: int64
error

Why the request was refused, in plain words. Present when success is false.

string | null
success
required

true when the request did what it asked. false is a refusal: read error.

boolean
Example
{
"success": true,
"data": {
"state": "authorized",
"tournament_id": 1908,
"entry_fee_atomic": "1000000000000000000000",
"entry_fee_foxsy": 1000,
"max_entries": 2,
"allowed_wallets": [
"erd1creator000000000000000000000000000000000000000000000000000",
"erd1target0000000000000000000000000000000000000000000000000000"
],
"expiry": 1792300600,
"signature_hex": "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab",
"sc_address": "erd1qqqqqqqqqqqqqpgqfw3nadv3z7hj6c2x7alndtw03y5ang43utesz8nmg9",
"foxsy_token_id": "FOXSY-abc123",
"gas_limit": 18480000,
"expires_in_seconds": 600
}
}

No valid session. The body is plain text: Missing Authorization header, Invalid Authorization format, Invalid session token or Session expired or invalid. Sign in again.

Media type application/json

The answer of a request that returns no data. POST /api/tournaments/{id}/delete answers its refusals with it: success is false and error says why.

object
code

A machine-readable reason next to error, set only where the caller must act differently.

string
data

Always null on success: the route returns no data.

error

Why the request was refused, in plain words. Present when success is false.

string
success
required

true when the request did what it asked. false is a refusal: read error.

boolean
Example generated
{
"code": "example",
"data": "example",
"error": "example",
"success": true
}