Skip to content

Authorize a paid 1v1 challenge 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, "format": "example", "has_third_place_match": true, "home_and_away": true, "max_teams": 1, "min_teams": 1, "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 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 for a Silver or Gold challenge with max_teams: 2. The server runs every check the normal create runs, writes the challenge in a hidden state that only you can see and answers the signed terms: the exact fee, the allowed wallets in order, the expiry in chain time and the server’s signature. Build the transaction from them exactly as answered and pay; the contract refuses any other amount, wallet, order or key. Then call POST /api/tournaments/challenges/{id}/confirm.

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 can still be paid is refused until its window closes. When a previous authorization of yours turns out paid, the answer has state: "open" and its tournament_id: open that challenge, there is nothing to sign.

Offered only while GET /api/config answers tournament_defaults.challenge_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 or the contract does not offer it yet.
  • 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.: an operator or contract wallet.
  • The one-signature flow creates paid 1v1 challenges only (Silver or Gold, two seats). Create other tournaments the usual way.: the body is not a paid 1v1 challenge.
  • Developer challenges are created the usual way.: the body has developer_mode: true. A developer challenge is created with POST /api/tournaments and joined with POST /api/tournaments/{id}/join, which runs the developer checks.
  • You already started a challenge for <fee> FOXSY against <team>. Finish it in the wallet or wait <N>s to start a different one.: a previous authorization of yours can still be paid.
  • 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).
  • Every refusal of POST /api/tournaments for a 1v1 challenge, in the same words.
  • 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. 0 or absent for Bronze. Silver and Gold need a fee inside the tier’s range; the refusal names the range.

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, 8 for groups_knockout, 36 for league_knockout and 3 for round_robin and swiss; at most max_teams. Equal to max_teams for knockout, groups_knockout and league_knockout.

integer format: int32
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, split equally among the 8 quarter-finalists by the tournaments contract. 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 (free), silver or gold (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; at most max_teams ids.

Array<integer> | null
Example generated
{
"challenge_deadline_minutes": 1,
"deadline": "2026-04-15T12:00:00Z",
"description": "example",
"developer_mode": true,
"entry_fee_foxsy": 1,
"format": "example",
"has_third_place_match": true,
"home_and_away": true,
"max_teams": 1,
"min_teams": 1,
"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.

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 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.

integer format: int64
max_entries
required

The seat cap to send: 2.

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 and expires_in_seconds the seconds to wait before a different one can start. Any other value is the status of a previous challenge of yours that turned out paid (open): nothing to sign, go to tournament_id.

string
tournament_id
required

The challenge id: 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
}