Codebattle Public API v1
A small, me-centric HTTP API for bots, CLIs, editors and devices: see your live game and history, start games against Codebot or a friend, join open games from the lobby, join tournaments and run your own.
- Base URL:
https://codebattle.hexlet.io/public_api/v1 - Format: JSON in, JSON out (
Content-Type: application/jsonon requests with a body) - Machine-readable spec:
/public_api/v1/openapi.json(OpenAPI 3.1) - This page as Markdown:
/api-docs.md, handy for pasting into an LLM
Quickstart
- Open Settings → API tokens on codebattle and create a token. Copy it right away: it is
shown only once and looks like
cbp_.... - Call the API with the token in the
Authorizationheader:
export CODEBATTLE_TOKEN=cbp_your_token_here
curl -s https://codebattle.hexlet.io/public_api/v1/me \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
{"user": {"id": 42, "name": "ada", "avatar_url": null, "rating": 1461, "rank": 12, "lang": "dlang"}}
- Start a game against Codebot (needs the
games:writescope) and watch it:
curl -s -X POST https://codebattle.hexlet.io/public_api/v1/games \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"level": "easy", "opponent": "bot"}'
curl -s https://codebattle.hexlet.io/public_api/v1/me/active_game \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
You solve the task in the browser at the game url; the API tells you what is going on.
The same from Ruby, without gems. Note the explicit Content-Type: without it Net::HTTP
sends the body as a form and the API answers 415.
require "net/http"
require "json"
uri = URI("https://codebattle.hexlet.io/public_api/v1/games")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{ENV["CODEBATTLE_TOKEN"]}",
"Content-Type" => "application/json")
req.body = {level: "easy", opponent: "bot"}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
Authentication
Every request needs a personal token in the header (query parameters are never accepted):
Authorization: Bearer cbp_...
A token belongs to one user, acts as that user and carries scopes:
| Scope | Allows |
|---|---|
read |
All GET endpoints |
games:write |
Create, join and cancel games; send, accept and cancel invites; join and leave tournaments |
tournaments:write |
Create, edit, start and cancel your tournaments |
- Tokens with write scopes must have an expiry (30, 90 or 365 days) and can be created only by accounts older than 7 days. A read-only token may live forever.
- A user can have up to 10 active tokens. Tokens are revoked when you change your password, and stop working immediately if the account is banned or archived.
- Treat a token like a password: keep it out of git and client-side code.
Conventions
- Times are ISO 8601 in UTC, e.g.
2026-10-10T19:03:18Z. - Countdowns: endpoints that matter for timing return
server_time. Compute remaining time asends_at - server_timeinstead of trusting the device clock. - Privacy by 404: an object that exists but is not related to you answers exactly like a missing
one (
404 not_found), so probing ids reveals nothing. - JSON bodies: send request bodies as JSON with
Content-Type: application/json. Many HTTP clients (RubyNet::HTTP,curl -d) default to form encoding; such requests get415. - Strict bodies: unknown fields in a request body are an error (
422), not silently ignored. - Live games hide the opponent's progress:
result_percentof other players appears only after the game is over. - Versioning: v1 only gets additive changes (new endpoints, new fields). Ignore fields you don't know.
Pagination
GET /me/games uses keyset (cursor) pagination, which stays correct while new games keep arriving:
- Request the first page without
cursor: you get up to 50 games, newest first, andnext_cursor. - Pass
next_cursorback ascursorto get the next, older page. next_cursor: nullmeans there are no more pages.
The cursor is simply the id of the last game on the page. To get games from a period instead of
walking pages, use from and to (ISO 8601 dates or datetimes, by game start time). They combine
with cursor if a period has more than 50 games.
curl -s "https://codebattle.hexlet.io/public_api/v1/me/games?from=2026-10-01&to=2026-10-08" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
The tournament ranking for creators and moderators uses page / page_size instead.
Errors
Every error has the same shape:
{"error": {"code": "validation_failed", "message": "Invalid parameters: level is required", "details": {"level": "is required"}}}
For validation_failed, message lists every failed field and details maps each field to what
is wrong with it, for programs.
| HTTP | code |
Meaning |
|---|---|---|
| 401 | unauthorized |
No Authorization: Bearer header |
| 401 | invalid_token |
The token is unknown, expired or revoked |
| 403 | insufficient_scope |
The token lacks the scope this endpoint needs |
| 404 | not_found |
Missing, or not related to you |
| 415 | unsupported_media_type |
A request body sent without Content-Type: application/json |
| 422 | validation_failed |
Bad or unknown parameters, or an action not allowed in the current state |
| 429 | rate_limited |
Too many requests, see below |
| 429 | quota_exceeded |
Too many games or tournaments created |
| 503 | api_disabled |
The API (or its write operations) is temporarily switched off |
Event stream
GET /me/events is a live stream of everything that happens to you, delivered as
Server-Sent Events (SSE). It is
an ordinary HTTP request that stays open: the server keeps writing events into the response as
they happen. Use it instead of polling to react to invites, game starts and tournament rounds
within a second.
curl -sN https://codebattle.hexlet.io/public_api/v1/me/events \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Accept: text/event-stream"
Wire format
The response is text/event-stream. Each event is a few field: value lines followed by a blank
line; data is always one line of JSON with a server_time:
id: 1791702012345
event: invite.created
data: {"invite":{"id":9001,"direction":"incoming","creator":{"id":77,"name":"grace","rating":1520,"avatar_url":null},...},"server_time":"2026-10-11T07:00:12Z"}
: ping
- Lines starting with
:are comments.: pingarrives every 15 seconds so proxies keep the connection; ignore it (or use it to detect a dead connection: no bytes for 45 s → reconnect). retry: 3000at the start tellsEventSourceclients to reconnect after 3 seconds.idis a timestamp in milliseconds, for logging. Events are not replayed: after a reconnect thehelloevent brings the current state instead, so nothing is lost.
Lifecycle
- You connect and immediately get
hellowith your current state. - Events arrive as things happen.
- After an hour the server sends
byeand closes the stream. Reconnect right away. - If the connection drops for any other reason, reconnect after a few seconds.
At most 2 streams per user can be open at the same time; a third request gets 429 rate_limited.
Events
| Event | When | Payload (data) |
|---|---|---|
hello |
Right after connecting | user (Player), active_game (Game or null), invites (Invite[]), tournaments (unfinished Tournament[] you play in or run) |
invite.created |
Someone invited you, or you sent an invite | invite |
invite.accepted |
An invite between you and someone was accepted | invite with game_id, game_url |
invite.canceled |
Declined by the recipient or withdrawn by the creator | invite |
invite.dropped |
Dropped because one of you started another game | invite |
game.created |
You created a game that waits for an opponent | game |
game.started |
A game with you started: against a bot, from an invite, someone joined your game, or a tournament round paired you | game |
game.finished |
Your game ended (game_over or timeout); results are visible |
game |
game.canceled |
Your game was canceled before it finished | game |
tournament.joined |
You joined a tournament | tournament |
tournament.left |
You left a tournament | tournament |
tournament.player_joined |
Someone joined a tournament you created or moderate | tournament, player (Player) |
tournament.player_left |
Someone left a tournament you created or moderate | tournament, player_id |
tournament.started |
A tournament you play in or run started | tournament |
tournament.round_started |
A new round began; your game arrives as game.started |
tournament |
tournament.round_finished |
A round ended; tournament.break.active and ends_at tell when the next round starts |
tournament |
tournament.finished |
The tournament is over | tournament |
tournament.canceled |
The tournament was canceled | tournament |
tournament.restarted |
An admin restarted the tournament | tournament |
bye |
The server is closing the stream; reconnect | reason |
game, tournament and invite have the same fields as in the REST endpoints (see
Objects). New event types may appear in v1; skip the ones you don't know.
Clients
Ruby, standard library only:
require "net/http"
require "json"
uri = URI("https://codebattle.hexlet.io/public_api/v1/me/events")
loop do
Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 45) do |http|
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{ENV["CODEBATTLE_TOKEN"]}",
"Accept" => "text/event-stream")
http.request(req) do |res|
buffer = +""
res.read_body do |chunk|
buffer << chunk
while (block = buffer.slice!(/\A.*?\n\n/m))
name = block[/^event: (.+)$/, 1]
data = block[/^data: (.+)$/, 1]
next unless name && data # pings and retry hints
event = JSON.parse(data)
case name
when "invite.created"
puts "Invite from #{event["invite"]["creator"]["name"]}" if event["invite"]["direction"] == "incoming"
when "game.started"
system("firefox", "--kiosk", event["game"]["url"])
end
end
end
end
end
rescue StandardError => e
warn "stream dropped: #{e.message}"
ensure
sleep 3
end
Python with requests:
import json, os, time, requests
URL = "https://codebattle.hexlet.io/public_api/v1/me/events"
HEADERS = {"Authorization": f"Bearer {os.environ['CODEBATTLE_TOKEN']}", "Accept": "text/event-stream"}
while True:
try:
with requests.get(URL, headers=HEADERS, stream=True, timeout=(10, 45)) as res:
name = None
for line in res.iter_lines(decode_unicode=True):
if line.startswith("event: "):
name = line[7:]
elif line.startswith("data: ") and name:
print(name, json.loads(line[6:]))
elif line == "":
name = None
except requests.RequestException as error:
print("stream dropped:", error)
time.sleep(3)
JavaScript (Node 18+ or a browser; EventSource can't send the Authorization header, so
read the stream with fetch):
const res = await fetch("https://codebattle.hexlet.io/public_api/v1/me/events", {
headers: { Authorization: `Bearer ${process.env.CODEBATTLE_TOKEN}`, Accept: "text/event-stream" },
});
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break; // reconnect here
buffer += value;
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) {
const block = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const name = block.match(/^event: (.+)$/m)?.[1];
const data = block.match(/^data: (.+)$/m)?.[1];
if (name && data) console.log(name, JSON.parse(data));
}
}
If your environment can't keep a connection open (some corporate proxies, cron jobs), use
long polling on GET /me/invites?wait=25 instead, or poll the REST endpoints.
Limits
Limits are per user, shared by all of the user's tokens.
| What | Limit |
|---|---|
| Requests | 60 per minute. Responses carry x-ratelimit-limit and x-ratelimit-remaining |
404 responses |
More than 30 in 10 minutes blocks the API for an hour |
| Games created | 5 per minute, 30 per day |
| Tournaments created | 5 per day, at most 3 unfinished at a time |
| Event streams | 2 open at a time; each lives up to an hour |
Polling GET /me/active_game every 5–10 seconds fits comfortably in the limit, but the
event stream is better: one connection, events within a second.
Recipes
Device that vibrates 10 minutes before the end of a game. Listen to the event stream: on
game.started store game.ends_at - server_time as the remaining time and count down locally;
game.finished or game.canceled stops the countdown. (Without a stream, poll
GET /me/active_game.)
Join an open game from the lobby. List the games waiting for an opponent, pick one and join
it (needs games:write):
curl -s "https://codebattle.hexlet.io/public_api/v1/games/live?state=waiting_opponent" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" | jq '.games[] | {id, level, timeout_seconds, players}'
curl -s -X POST https://codebattle.hexlet.io/public_api/v1/games/12345/join \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
The game starts at once: open its url. The task of an open game is hidden until someone joins.
You can play one game at a time; if you are in another one, the join answers 422 with
details.reason already_in_a_game and details.active_game_id: finish that game, or cancel it
with POST /games/{id}/cancel while it still waits for an opponent. To wait for someone to join
your own game instead, create one with {"opponent": "link"} and share its join_url.
Join a tournament without keeping its page open. Find it in GET /tournaments/schedule (or
GET /tournaments/live) and join it (needs games:write); for a private tournament pass the
access_token from its invite_url:
curl -s -X POST https://codebattle.hexlet.io/public_api/v1/tournaments/777/join \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
curl -s -X POST https://codebattle.hexlet.io/public_api/v1/tournaments/778/join \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"access_token": "from_the_invite_url"}'
The card comes back with joined: true (joining twice is fine). Now listen to the
event stream and open the browser only on game.started: its game.url is your
first game. A phone saves a lot of battery compared with a tournament tab open for ten minutes
before the start. Changed your mind? POST /tournaments/{id}/leave works until the start. A
failed join answers 422 with details.reason: not_open, full or not_running.
Bot that runs a private tournament. POST /tournaments with name, description,
starts_at, level, send players the returned invite_url, then POST /tournaments/{id}/start
when everyone has joined, and read GET /tournaments/{id}/ranking.
React to invites when you can't hold a stream open. The event stream is
the first choice. Without it, long-poll GET /me/invites:
version=""
while true; do
resp=$(curl -s "https://codebattle.hexlet.io/public_api/v1/me/invites?wait=25&version=$version" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN")
version=$(echo "$resp" | jq -r .version)
echo "$resp" | jq '.invites[] | select(.direction == "incoming")'
done
The request returns as soon as the list changes (or after 25 s), so a new invite shows up within
a second at about two requests a minute. Accept it with POST /invites/{id}/accept and open
game_url.
One voice command to a private tournament. POST /tournaments with your preset of fields,
show invite_url as a QR code (the tournament page has a "Show QR" button too), wait for
tournament.player_joined on the event stream, then
POST /tournaments/{id}/start and open the game from the game.started event.
A game on a chosen task. List the tasks of a level with GET /tasks?level=medium (add
tags=strings,math or name=partition to narrow it), then pass the id or the exact name:
POST /games with {"task_name": "minimal_time_partition", "opponent": "bot"}. The level comes
from the task. Invites take the same task_id / task_name. You only see and can pick public
tasks and your own.
Export your history. Walk GET /me/games with cursor until next_cursor is null, or ask for
one period with from / to.
Endpoints
All paths are relative to https://codebattle.hexlet.io/public_api/v1.
| Method | Path | What it does | Scope |
|---|---|---|---|
GET |
/me |
Current user | read |
GET |
/me/active_game |
Current live game (null if none). Count down with ends_at - server_time. | read |
GET |
/me/games |
My finished games, newest first | read |
GET |
/me/tournaments |
Tournaments I created, moderate or play | read |
GET |
/me/invites |
My pending invites, incoming and outgoing | read |
GET |
/me/events |
Live stream of everything that happens to you (SSE) | read |
GET |
/tournaments/schedule |
Public upcoming and active tournaments for the next 14 days | read |
GET |
/tournaments/live |
Tournaments running now or open for joining | read |
POST |
/tournaments |
Create a private tournament (always grade open, tasks by level) | tournaments:write |
GET |
/tournaments/{id} |
Tournament card: related to you, or public and scheduled | read |
PATCH |
/tournaments/{id} |
Edit your tournament before it starts | tournaments:write |
GET |
/tournaments/{id}/ranking |
Ranking: full and paginated for the creator/moderators, top 20 plus your row for participants | read |
POST |
/tournaments/{id}/start |
Start your tournament | tournaments:write |
POST |
/tournaments/{id}/cancel |
Cancel your tournament | tournaments:write |
POST |
/tournaments/{id}/join |
Join a tournament as a player | games:write |
POST |
/tournaments/{id}/leave |
Leave a tournament before it starts | games:write |
GET |
/tasks |
Tasks you can play | read |
GET |
/games/live |
Live games | read |
POST |
/games |
Create a game: against a bot, or waiting for an opponent who joins by link | games:write |
POST |
/games/{id}/join |
Join a game that waits for an opponent | games:write |
POST |
/games/{id}/cancel |
Cancel your game that waits for an opponent | games:write |
POST |
/invites |
Invite a user to a game | games:write |
POST |
/invites/{id}/accept |
Accept an invite sent to you | games:write |
POST |
/invites/{id}/cancel |
Decline or withdraw an invite | games:write |
GET |
/users/online |
Who is online | read |
Me
GET/me
Current user
Who the token belongs to. A cheap way to check that a token works.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/me" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"user": {
"id": 42,
"name": "ada",
"avatar_url": "https://avatars.githubusercontent.com/u/1?v=4",
"rating": 1461,
"rank": 12,
"lang": "dlang"
}
}
Errors: 401, 403, 404, 429
GET/me/active_game
Current live game (null if none). Count down with ends_at - server_time.
Your live game, or null: the one you are playing, or one you created that still waits for an opponent (state: waiting_opponent). Poll it (every few seconds is fine) to notice a game started from the web or by a tournament. Compute the remaining time as ends_at - server_time, not against your own clock.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/me/active_game" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"game": {
"id": 324274,
"url": "https://codebattle.hexlet.io/games/324274",
"state": "playing",
"level": "easy",
"type": "duo",
"starts_at": "2026-10-10T18:40:00Z",
"timeout_seconds": 600,
"ends_at": "2026-10-10T18:50:00Z",
"finishes_at": null,
"tournament_id": null,
"task": {
"id": 1201,
"name": "pair_with_maximum_product",
"level": "easy",
"tags": [
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/1201"
},
"players": [
{
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461,
"rank": 12,
"lang": "python",
"is_bot": false,
"result": null,
"result_percent": 60.0
},
{
"id": -1,
"name": "Codebot",
"avatar_url": null,
"rating": 1200,
"rank": null,
"lang": "js",
"is_bot": true,
"result": null
}
]
},
"server_time": "2026-10-10T18:43:20Z"
}
Errors: 401, 403, 404, 429
GET/me/games
My finished games, newest first
Finished games (game_over or timeout), 50 per page, newest first. Pagination is keyset-based: next_cursor is the id of the last game on the page; pass it back as cursor to get the next, older page. next_cursor is null on the last page. from/to narrow the result by game start time and combine with cursor.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
cursor |
integer | next_cursor from the previous page: returns games with a smaller id. |
from |
datetime | Only games started at or after this moment. ISO 8601 date (2026-10-01, midnight UTC) or datetime. |
to |
datetime | Only games started before this moment. ISO 8601 date or datetime. |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/me/games?from=2026-10-01&to=2026-10-10T18:00:00Z" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"games": [
{
"id": 324274,
"url": "https://codebattle.hexlet.io/games/324274",
"state": "game_over",
"level": "easy",
"type": "duo",
"starts_at": "2026-10-10T18:40:00Z",
"timeout_seconds": 600,
"ends_at": "2026-10-10T18:50:00Z",
"finishes_at": "2026-10-10T18:46:12Z",
"tournament_id": null,
"task": {
"id": 1201,
"name": "pair_with_maximum_product",
"level": "easy",
"tags": [
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/1201"
},
"players": [
{
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461,
"rank": 12,
"lang": "python",
"is_bot": false,
"result": "won",
"result_percent": 100.0
},
{
"id": -1,
"name": "Codebot",
"avatar_url": null,
"rating": 1200,
"rank": null,
"lang": "js",
"is_bot": true,
"result": "lost",
"result_percent": 40.0
}
]
}
],
"next_cursor": 324100
}
Errors: 401, 403, 404, 422, 429
GET/me/tournaments
Tournaments I created, moderate or play
Up to 50 tournaments you created, moderate or joined as a player, newest first.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
state |
string | Filter by state, e.g. active or finished. |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/me/tournaments" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournaments": [
{
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
]
}
Errors: 401, 403, 404, 429
GET/me/invites
My pending invites, incoming and outgoing
Pending invites where you are the creator or the recipient. Supports long polling instead of frequent polls: pass wait (seconds, up to 30) and the version from your previous response. If the list already differs from that version the answer is immediate; otherwise the request is held until an invite is created, accepted or canceled, or until wait runs out, and then returns the current list. Loop on it to react to invites within a second with about two requests a minute. An accepted invite leaves the list; the creator finds the game via GET /me/active_game.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
wait |
integer | Hold the request up to this many seconds (0..30) while nothing changes. |
version |
string | version from the previous response. |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/me/invites?wait=25&version=1x3kq9" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"invites": [
{
"id": 9001,
"state": "pending",
"direction": "incoming",
"level": "easy",
"timeout_seconds": 600,
"creator": {
"id": 77,
"name": "grace",
"avatar_url": null,
"rating": 1520
},
"recipient": {
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461
},
"game_id": null,
"game_url": null,
"created_at": "2026-10-10T19:01:00Z"
}
],
"version": "1x3kq9",
"server_time": "2026-10-10T19:01:05Z"
}
Errors: 401, 403, 404, 422, 429
GET/me/events
Live stream of everything that happens to you (SSE)
A Server-Sent Events stream: one request that stays open. It starts with hello (your current state: active game, pending invites, unfinished tournaments), then sends one event per change: invites, your games, tournaments you play in or run. A : ping comment comes every 15 seconds. The server closes the stream after an hour with a bye event; reconnect and you get a fresh hello. At most 2 open streams per user. See Event stream above for every event and client examples.
Scope: read
Example request
curl -sN "https://codebattle.hexlet.io/public_api/v1/me/events" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Accept: text/event-stream"
Response 200 (text/event-stream, stays open)
retry: 3000
id: 1791702000000
event: hello
data: {"user":{"id":42,"name":"ada",...},"active_game":null,"invites":[],"tournaments":[],"server_time":"2026-10-11T07:00:00Z"}
id: 1791702012345
event: invite.created
data: {"invite":{"id":9001,"direction":"incoming","creator":{"id":77,"name":"grace",...},...},"server_time":"2026-10-11T07:00:12Z"}
: ping
id: 1791702020000
event: game.started
data: {"game":{"id":324274,"state":"playing","url":"https://codebattle.hexlet.io/games/324274",...},"server_time":"2026-10-11T07:00:20Z"}
Errors: 401, 403, 429
Tournaments
GET/tournaments/schedule
Public upcoming and active tournaments for the next 14 days
Public tournaments that start between 1 day ago and 14 days ahead and are not finished yet: what the lobby shows as upcoming. For everything running right now use GET /tournaments/live.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/tournaments/schedule" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournaments": [
{
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
]
}
Errors: 401, 403, 404, 429
GET/tournaments/live
Tournaments running now or open for joining
Currently running tournament processes in waiting_participants or active state: public ones plus private ones you created, moderate or play in. Uses live state and participant counts, excluding abandoned database records. A running tournament can have an old starts_at. Newest first, at most 100.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/tournaments/live" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournaments": [
{
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
]
}
Errors: 401, 403, 404, 429
POST/tournaments
Create a private tournament (always grade open, tasks by level)
Creates a private (access_type: token) tournament of grade open; tasks are picked by level. Share invite_url with players. Quota: 5 per day and at most 3 unfinished API-created tournaments.
Scope: tournaments:write
Request body
| Field | Type | Description |
|---|---|---|
name (required) |
string | 3..100 characters |
description (required) |
string | 3..2000 characters |
starts_at (required) |
datetime | In the future, at most 30 days ahead |
level (required) |
string | One of elementary, easy, medium, hard |
players_limit |
integer | 2..64 |
rounds_limit |
integer | 1..10 |
timeout_mode |
string | One of per_task, per_round_fixed |
match_timeout_seconds |
integer | 60..3600 |
round_timeout_seconds |
integer | 60..3600 |
break_duration_seconds |
integer | 0..600 |
use_chat |
boolean |
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/tournaments" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"ada","description":"Weekly practice for our team","starts_at":"2026-10-10T19:03:18Z","level":"elementary"}'
Response 201
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 415, 422, 429
GET/tournaments/{id}
Tournament card: related to you, or public and scheduled
Works for tournaments related to you and for public ones. The creator and moderators also get access_token and invite_url.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/tournaments/123" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 429
PATCH/tournaments/{id}
Edit your tournament before it starts
Only the fields you send change. Allowed while the tournament is upcoming or waiting_participants.
Scope: tournaments:write
Request body
| Field | Type | Description |
|---|---|---|
name |
string | 3..100 characters |
description |
string | 3..2000 characters |
starts_at |
datetime | In the future, at most 30 days ahead |
level |
string | One of elementary, easy, medium, hard |
players_limit |
integer | 2..64 |
rounds_limit |
integer | 1..10 |
timeout_mode |
string | One of per_task, per_round_fixed |
match_timeout_seconds |
integer | 60..3600 |
round_timeout_seconds |
integer | 60..3600 |
break_duration_seconds |
integer | 0..600 |
use_chat |
boolean |
Example request
curl -s -X PATCH "https://codebattle.hexlet.io/public_api/v1/tournaments/123" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 415, 422, 429
GET/tournaments/{id}/ranking
Ranking: full and paginated for the creator/moderators, top 20 plus your row for participants
The creator and moderators get the full ranking with page / page_size (max 100). Participants get the top 20 and their own row in me.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
page |
integer | |
page_size |
integer |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/tournaments/123/ranking" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"ranking": [
{
"id": 42,
"name": "ada",
"place": 1,
"score": 42,
"lang": "python"
}
],
"me": {
"id": 42,
"name": "ada",
"place": 1,
"score": 42,
"lang": "python"
},
"page_info": {
"page_number": 1,
"page_size": 1,
"total_entries": 1
}
}
Errors: 401, 403, 404, 429
POST/tournaments/{id}/start
Start your tournament
Starts your tournament now with the players who have joined.
Scope: tournaments:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/tournaments/123/start" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 422, 429
POST/tournaments/{id}/cancel
Cancel your tournament
Cancels your tournament and finishes its games.
Scope: tournaments:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/tournaments/123/cancel" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 429
POST/tournaments/{id}/join
Join a tournament as a player
Same as the Join button on the tournament page: works for a public tournament from the schedule, or a private one when you pass the access_token from its invite_url (without it, a private tournament you are not related to answers 404). Possible while the tournament is waiting_participants, and for swiss and ladder also while it is active. Joining twice is not an error: you get the card with joined: true. Then wait for tournament.started / game.started on GET /me/events and open the game url only when it begins, instead of keeping the tournament page open. A failed join answers 422 validation_failed with details.reason: not_open (finished, canceled or not open yet), full (players_limit reached), not_running (the tournament process is stopped; an organizer has to restart it) or rejected (the tournament didn't accept the join).
Scope: games:write
Request body
| Field | Type | Description |
|---|---|---|
access_token |
string | The access_token query parameter of a private tournament's invite_url |
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/tournaments/123/join" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 422, 429
POST/tournaments/{id}/leave
Leave a tournament before it starts
Removes you from the players while the tournament is waiting_participants. Leaving a tournament you don't play in returns the card with joined: false. After the start it answers 422 validation_failed with details.reason already_started; not_running means the tournament process is stopped.
Scope: games:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/tournaments/123/leave" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tournament": {
"id": 5775,
"name": "Friday practice",
"url": "https://codebattle.hexlet.io/tournaments/5775",
"type": "swiss",
"state": "active",
"level": "easy",
"grade": "open",
"access_type": "token",
"starts_at": "2026-10-10T19:00:00Z",
"started_at": "2026-10-10T19:00:04Z",
"players_count": 8,
"players_limit": 16,
"rounds_limit": 3,
"current_round": {
"position": 1,
"started_at": "2026-10-10T19:12:00Z",
"ends_at": "2026-10-10T19:22:00Z"
},
"break": {
"active": false,
"ends_at": null
},
"access_token": "Xy7pQw2LmN4rTz8aB",
"invite_url": "https://codebattle.hexlet.io/tournaments/5775?access_token=Xy7pQw2LmN4rTz8aB"
}
}
Errors: 401, 403, 404, 422, 429
Tasks
GET/tasks
Tasks you can play
Public active tasks plus your own, ordered by level and name. Use an id or name from here in POST /games or POST /invites.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
level |
string | Only tasks of this level. |
tags |
string | Comma-separated; tasks having any of these tags. |
name |
string | Case-insensitive substring of the task name. |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/tasks?level=medium&tags=strings,math" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"tasks": [
{
"id": 42,
"name": "minimal_time_partition",
"level": "medium",
"tags": [
"math",
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/42"
}
]
}
Errors: 401, 403, 422, 429
Games
GET/games/live
Live games
What the lobby shows: games being played and games waiting for an opponent (usually a few Codebot games are waiting), newest first. Tournament games and other people's link games are not listed. Pass state=waiting_opponent for just the games you can join, then join one with POST /games/{id}/join; their task stays hidden until someone joins. Changes often; poll every 10–30 seconds.
Scope: read
Query parameters
| Name | Type | Description |
|---|---|---|
state |
string | Only games in this state: waiting_opponent (open to join) or playing. |
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/games/live?state=waiting_opponent" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"games": [
{
"id": 324274,
"url": "https://codebattle.hexlet.io/games/324274",
"state": "game_over",
"level": "easy",
"type": "duo",
"starts_at": "2026-10-10T18:40:00Z",
"timeout_seconds": 600,
"ends_at": "2026-10-10T18:50:00Z",
"finishes_at": "2026-10-10T18:46:12Z",
"tournament_id": null,
"task": {
"id": 1201,
"name": "pair_with_maximum_product",
"level": "easy",
"tags": [
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/1201"
},
"players": [
{
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461,
"rank": 12,
"lang": "python",
"is_bot": false,
"result": "won",
"result_percent": 100.0
},
{
"id": -1,
"name": "Codebot",
"avatar_url": null,
"rating": 1200,
"rank": null,
"lang": "js",
"is_bot": true,
"result": "lost",
"result_percent": 40.0
}
]
}
],
"server_time": "2026-10-10T19:03:18Z"
}
Errors: 401, 403, 422, 429
POST/games
Create a game: against a bot, or waiting for an opponent who joins by link
opponent: bot starts the game immediately against Codebot. opponent: link creates a hidden game waiting for an opponent; send them join_url. Pick the task with task_id or task_name (see GET /tasks), or let the server pick a random one of level. Only tasks you may play are accepted: public ones and your own; any other answers 422 no such task. You can have only one active game at a time. Quota: 5 games per minute, 30 per day.
Scope: games:write
Request body
| Field | Type | Description |
|---|---|---|
level |
string | Required unless task_id or task_name is given; with a task it must equal the task's level.. One of elementary, easy, medium, hard |
task_id |
integer | Play this task (from GET /tasks). Pass this or task_name; without both a random task of level is picked. |
task_name |
string | Exact task name, e.g. minimal_time_partition |
opponent (required) |
string | One of bot, link |
timeout_seconds |
integer | 60..3600 |
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/games" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"opponent":"bot"}'
Response 201
{
"game": {
"id": 324274,
"url": "https://codebattle.hexlet.io/games/324274",
"state": "waiting_opponent",
"level": "easy",
"type": "duo",
"starts_at": "2026-10-10T19:03:18Z",
"timeout_seconds": 600,
"ends_at": "2026-10-10T19:03:18Z",
"finishes_at": "2026-10-10T19:03:18Z",
"tournament_id": null,
"task": {
"id": 42,
"name": "minimal_time_partition",
"level": "medium",
"tags": [
"math",
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/42"
},
"players": [
{
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461,
"rank": 12,
"lang": "python",
"is_bot": false,
"result": "won",
"result_percent": 100.0
}
],
"join_url": "string"
}
}
Errors: 401, 403, 404, 415, 422, 429
POST/games/{id}/join
Join a game that waits for an opponent
Joins a game from GET /games/live?state=waiting_opponent, or a link game whose id someone shared with you. The game starts at once; open its url. A failed join answers 422 validation_failed with details.reason: not_waiting_opponent (the game already started), already_in_a_game (you play another game: details.active_game_id; finish it or POST /games/{id}/cancel it if it still waits) or already_joined (you are already a player of this game).
Scope: games:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/games/123/join" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"game": {
"id": 324274,
"url": "https://codebattle.hexlet.io/games/324274",
"state": "game_over",
"level": "easy",
"type": "duo",
"starts_at": "2026-10-10T18:40:00Z",
"timeout_seconds": 600,
"ends_at": "2026-10-10T18:50:00Z",
"finishes_at": "2026-10-10T18:46:12Z",
"tournament_id": null,
"task": {
"id": 1201,
"name": "pair_with_maximum_product",
"level": "easy",
"tags": [
"collections"
],
"visibility": "public",
"url": "https://codebattle.hexlet.io/tasks/1201"
},
"players": [
{
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461,
"rank": 12,
"lang": "python",
"is_bot": false,
"result": "won",
"result_percent": 100.0
},
{
"id": -1,
"name": "Codebot",
"avatar_url": null,
"rating": 1200,
"rank": null,
"lang": "js",
"is_bot": true,
"result": "lost",
"result_percent": 40.0
}
]
}
}
Errors: 401, 403, 404, 422, 429
POST/games/{id}/cancel
Cancel your game that waits for an opponent
Only a game in waiting_opponent state can be canceled.
Scope: games:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/games/123/cancel" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"game": {
"id": 324274,
"state": "canceled"
}
}
Errors: 401, 403, 404, 422, 429
Invites
POST/invites
Invite a user to a game
Sends an invite that the recipient sees in the lobby and in GET /me/invites. One pending invite per pair of users.
Scope: games:write
Request body
| Field | Type | Description |
|---|---|---|
recipient_id |
integer | Whom to invite. Pass this or recipient_name. |
recipient_name |
string | Exact user name, handy for voice commands |
level |
string | One of elementary, easy, medium, hard |
task_id |
integer | Play this task (from GET /tasks). Pass this or task_name; without both a random task of level is picked. |
task_name |
string | Exact task name, e.g. minimal_time_partition |
timeout_seconds |
integer | Game length, default 3600. 60..3600 |
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/invites" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
Response 201
{
"invite": {
"id": 9001,
"state": "pending",
"direction": "incoming",
"level": "easy",
"timeout_seconds": 600,
"creator": {
"id": 77,
"name": "grace",
"avatar_url": null,
"rating": 1520
},
"recipient": {
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461
},
"task_id": null,
"game_id": null,
"game_url": null,
"created_at": "2026-10-10T19:01:00Z"
}
}
Errors: 401, 403, 404, 415, 422, 429
POST/invites/{id}/accept
Accept an invite sent to you
Starts the game for both players; the response has game_id and game_url. Fails with 422 if one of you is already in a game (the invite is then dropped).
Scope: games:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/invites/123/accept" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"invite": {
"id": 9001,
"state": "pending",
"direction": "incoming",
"level": "easy",
"timeout_seconds": 600,
"creator": {
"id": 77,
"name": "grace",
"avatar_url": null,
"rating": 1520
},
"recipient": {
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461
},
"task_id": null,
"game_id": null,
"game_url": null,
"created_at": "2026-10-10T19:01:00Z"
}
}
Errors: 401, 403, 404, 422, 429
POST/invites/{id}/cancel
Decline or withdraw an invite
The recipient declines, the creator withdraws. Only pending invites.
Scope: games:write
Example request
curl -s -X POST "https://codebattle.hexlet.io/public_api/v1/invites/123/cancel" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"invite": {
"id": 9001,
"state": "pending",
"direction": "incoming",
"level": "easy",
"timeout_seconds": 600,
"creator": {
"id": 77,
"name": "grace",
"avatar_url": null,
"rating": 1520
},
"recipient": {
"id": 42,
"name": "ada",
"avatar_url": null,
"rating": 1461
},
"task_id": null,
"game_id": null,
"game_url": null,
"created_at": "2026-10-10T19:01:00Z"
}
}
Errors: 401, 403, 404, 422, 429
Users
GET/users/online
Who is online
Registered users with codebattle open right now, highest rating first, with what they are doing. Changes slowly: polling every 30–60 seconds is plenty.
Scope: read
Example request
curl -s "https://codebattle.hexlet.io/public_api/v1/users/online" \
-H "Authorization: Bearer $CODEBATTLE_TOKEN"
Response 200
{
"users": [
{
"id": 77,
"name": "grace",
"avatar_url": null,
"rating": 1520,
"rank": 8,
"lang": "dlang",
"state": "playing",
"url": "https://codebattle.hexlet.io/games/324274"
}
],
"server_time": "2026-10-10T19:03:18Z"
}
Errors: 401, 403, 404, 429
Objects
Fields of the objects the endpoints return. Nullable fields may be null.
Player
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | |
avatar_url |
string, nullable | |
rating |
integer, nullable | |
rank |
integer, nullable | Place in the global rating |
lang |
string, nullable | Preferred programming language |
Game
| Field | Type | Description |
|---|---|---|
id |
integer | |
url |
string | |
state |
string | One of waiting_opponent, playing, game_over, timeout, canceled |
level |
string | |
type |
string | |
starts_at |
datetime, nullable | |
timeout_seconds |
integer | |
ends_at |
datetime, nullable | starts_at + timeout_seconds: when the game times out |
finishes_at |
datetime, nullable | |
tournament_id |
integer, nullable | |
task |
Task, nullable | null while the game waits for an opponent, unless it is your game |
players |
array of GamePlayer |
GamePlayer
Every field of Player, plus its own.
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | |
avatar_url |
string, nullable | |
rating |
integer, nullable | |
rank |
integer, nullable | Place in the global rating |
lang |
string, nullable | Language of the player's editor in this game |
is_bot |
boolean, nullable | |
result |
string, nullable | null until the player's game is decided. One of won, lost, gave_up, timeout, `` |
result_percent |
number, nullable | Hidden for opponents while the game is live |
Task
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | Unique, e.g. minimal_time_partition; usable as task_name |
level |
string | One of elementary, easy, medium, hard |
tags |
array of string | |
visibility |
string | hidden tasks are listed only for their creator. One of public, hidden |
url |
string |
Tournament
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | |
url |
string | |
type |
string | |
state |
string | One of upcoming, waiting_participants, active, finished, canceled, timeout |
level |
string | |
grade |
string | open for user tournaments; season grades for official ones |
access_type |
string | One of public, token |
starts_at |
datetime, nullable | |
started_at |
datetime, nullable | |
players_count |
integer, nullable | |
players_limit |
integer, nullable | |
rounds_limit |
integer, nullable | |
current_round |
object, nullable | Fields: position, started_at, ends_at |
break |
object | Fields: active, ends_at |
joined |
boolean | Whether you are a player of this tournament |
access_token |
string | Creator and moderators only |
invite_url |
string | Creator and moderators only |
RankingEntry
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | |
place |
integer | |
score |
number | |
lang |
string, nullable |
Invite
| Field | Type | Description |
|---|---|---|
id |
integer | |
state |
string | One of pending, accepted, canceled, expired, dropped |
direction |
string | incoming: you are the recipient and can accept. One of incoming, outgoing |
level |
string, nullable | |
timeout_seconds |
integer, nullable | |
creator |
Player, nullable | |
recipient |
Player, nullable | |
task_id |
integer, nullable | Task chosen by the creator, if any |
game_id |
integer, nullable | Set once accepted |
game_url |
string, nullable | |
created_at |
datetime |
OnlineUser
Every field of Player, plus its own.
| Field | Type | Description |
|---|---|---|
id |
integer | |
name |
string | |
avatar_url |
string, nullable | |
rating |
integer, nullable | |
rank |
integer, nullable | Place in the global rating |
lang |
string, nullable | Preferred programming language |
state |
string | The busiest of the user's open tabs. One of online, lobby, task, tournament, watching, playing |
url |
string, nullable | Game or tournament the user is on, if any |
Error
| Field | Type | Description |
|---|---|---|
error |
object | Fields: code, message, details |