Authorize a paid 1v1 challenge or tournament for one signed transaction
const url = 'https://foxleague.ai/api/tournaments/challenges/authorize';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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 hasdeveloper_mode: true. It is created withPOST /api/tournamentsand joined withPOST /api/tournaments/{id}/join, which runs the developer checks.Champions FoxLeague tournaments are created the usual way.:league_knockoutor asnapshot_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_onlyis 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 hassponsor_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/configanswers the exact seconds astournament_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/tournamentsfor 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.
Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”The settings of a new tournament or 1v1 challenge.
object
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.
Registration closes at this time (RFC 3339); it must be in the future. Required but ignored for a 1v1 challenge.
Optional text, at most 1000 characters.
Only teams with a verified developer binary may join. Available on every format except Champions FoxLeague.
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.
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.
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.
Adds a match for third place between the losing semifinalists. Default false.
round_robin only: every pair of teams meets twice. Ignored for other formats.
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.
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.
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.
Standard tournament only: you run the tournament without a seat and cannot join it later.
Optional highest best XI OVR a joining team may have: a whole number from 1 to 100, not below 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.
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.
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.
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.
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.
1v1 challenge only: the user id of the only opponent who may accept. Not your own id.
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.
Name of the tournament, 1 to 100 characters, not blank.
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.
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 ]}Responses
Section titled “ Responses ”Success or a refusal: read success (the refusals are in the description).
object
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.
The answer. Present when success is true.
object
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.
The exact amount the transaction must carry, in FOXSY atomic units, as a decimal string.
The entry fee in whole FOXSY.
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.
Chain time in seconds up to and including which the contract accepts the signature.
The FOXSY token to pay with.
The gas limit to send the transaction with. One figure for a challenge; for a tournament it grows with the allowed wallets.
The seat cap to send: 2 for a challenge, the tournament’s max_teams for a tournament.
The tournaments contract to send the transaction to.
The server’s 64-byte signature of the terms, as hexadecimal.
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.
The id of the challenge or tournament: the first argument of the transaction and the id to confirm.
Why the request was refused, in plain words. Present when success is false.
true when the request did what it asked. false is a refusal: read error.
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.
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
A machine-readable reason next to error, set only where the caller must act differently.
Always null on success: the route returns no data.
Why the request was refused, in plain words. Present when success is false.
true when the request did what it asked. false is a refusal: read error.
Example generated
{ "code": "example", "data": "example", "error": "example", "success": true}