Skip to content

API Reference

REST endpoints for the NextJudge data layer: contests, problems, submissions, users and authentication flows.

Base URL: http://localhost:5000 (dev). Routes under /v1/ unless noted.

Auth: Authentication. Send the raw JWT in the Authorization header with no Bearer prefix.

Terminal window
# 1. Register
RESP=$(curl -s -X POST http://localhost:5000/v1/basic_register \
-H "Content-Type: application/json" \
-d '{"name":"ada","email":"ada@example.com","password":"example-password"}')
TOKEN=$(echo $RESP | jq -r .token)
USER_ID=$(echo $RESP | jq -r .id)
# 2. Languages (no auth)
LANG_ID=$(curl -s http://localhost:5000/v1/languages | jq -r '.[] | select(.name=="python") | .id')
# 3. Submit
SUB_ID=$(curl -s -X POST http://localhost:5000/v1/submissions \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"user_id\":\"$USER_ID\",\"problem_id\":1,\"language_id\":\"$LANG_ID\",\"source_code\":\"print(1)\"}" \
| jq -r .id)
# 4. Poll
curl -s http://localhost:5000/v1/submissions/$SUB_ID -H "Authorization: $TOKEN" | jq .status

MethodPathAuth headerPurpose
POST/v1/basic_registernoneCreate account + JWT
POST/v1/basic_loginnoneLogin + JWT
POST/v1/create_or_login_userWEB_BRIDGE_SECRETOAuth bridge (web app)
POST/v1/login_judgeJUDGE_PASSWORDJudge worker JWT
POST/v1/basic_request_password_resetnoneRequest reset
POST/v1/basic_reset_passwordnoneSet new password

See Authentication for bodies and responses.


List users. Query: ?username= (filter by name).

Auth: admin

[{ "id": "uuid", "name": "ada", "email": "ada@example.com", "is_admin": false, "join_date": "..." }]

Auth: any authenticated user

{ "id": "uuid", "name": "ada", "email": "ada@example.com", "is_admin": false, "join_date": "..." }

Auth: admin

{ "name": "ada", "email": "ada@example.com", "image": "", "is_admin": false }

201 with user object. Duplicate name/email → 400.

Auth: admin. Update name, admin flag. 204 on success.

Soft-delete. Self or admin (not last admin). 204. Historical leaderboard rows show Deleted user.


Auth: required. Public problems for everyone; admins see all.

Query: ?query= (Elasticsearch when enabled).

Auth: required

Query: ?type=private includes hidden tests (admin only).

{
"id": 1,
"title": "Reverse String",
"identifier": "reverse-string",
"prompt": "...",
"difficulty": "EASY",
"public": true,
"test_cases": [{ "id": "uuid", "input": "hi", "expected_output": "ih", "hidden": false }],
"categories": [{ "id": "uuid", "name": "Strings" }]
}

Auth: admin

{
"title": "Reverse String",
"identifier": "reverse-string",
"prompt": "Reverse the input string.",
"source": "NextJudge",
"difficulty": "EASY",
"timeout": 5.0,
"accept_timeout": 5.0,
"execution_timeout": 5.0,
"memory_limit": 256,
"user_id": "admin-uuid",
"test_cases": [{ "input": "hello", "expected_output": "olleh", "hidden": false }],
"category_ids": ["uuid"],
"public": true
}

Validation (400 with {"code":"400","message":"..."}):

  • title, identifier and prompt must be non-empty (after trim)
  • difficulty required — one of VERY_EASY, EASY, MEDIUM, HARD, VERY_HARD
  • At least one test case; each test case needs non-empty input and expected_output

Auth: admin. Partial updates to problem metadata and tests. Same validation rules as POST when problem fields or test cases are included.

DELETE /v1/problems/{problem_description_id}

Section titled “DELETE /v1/problems/{problem_description_id}”

Auth: admin. 204.

All test cases for judging. Auth: judge or admin.

Auth: required

[{ "id": "uuid", "name": "DP" }]

Enqueue for grading. Auth: required

{
"user_id": "uuid",
"problem_id": 1,
"language_id": "uuid",
"source_code": "..."
}

201:

{ "id": "uuid", "status": "PENDING", "submit_time": "...", "source_code": "..." }

Judge PATCHes status later. You poll GET until status != PENDING.

Auth: submission owner, judge, or admin

Full submission including source_code and test_case_results after grading. Other authenticated users get 401 Unauthorized.

GET /v1/submissions/{submission_id}/status

Section titled “GET /v1/submissions/{submission_id}/status”

Auth: submission owner, judge, or admin. Lightweight status-only poll (preferred by the web app while waiting).

{ "id": "uuid", "status": "PENDING" }

Auth: judge or admin

{
"status": "ACCEPTED",
"stdout": "",
"stderr": "",
"time_elapsed": 0.042,
"failed_test_case_id": null,
"test_case_results": [{ "test_case_id": "uuid", "stdout": "...", "stderr": "", "passed": true }]
}

Auth: self or admin

GET /v1/user_problem_submissions/{user_id}/{problem_id}

Section titled “GET /v1/user_problem_submissions/{user_id}/{problem_id}”

Auth: self or admin. All attempts on one problem.


Auth: required. No test cases, arbitrary stdin.

{ "user_id": "uuid", "language_id": "uuid", "source_code": "print(input())", "stdin": "test" }

Also available unauthenticated on public/bench routes for demos (/v1/public/input_submissions, /v1/bench/input_submissions). Both POST routes are IP rate-limited (5/min, burst 2).

Authenticated POST limits (per user, in-memory per data-layer instance):

RouteLimit
POST /v1/input_submissions30/min, burst 10
POST /v1/submissions20/min, burst 5
POST /v1/basic_login, /v1/basic_register, password reset10/min per IP, burst 5

429 responses include Retry-After: 60 and {"code":"RATE_LIMIT_EXCEEDED",...}.

Poll until finished: true. Returns stdout, stderr, runtime.


No auth.

[{ "id": "uuid", "name": "python", "extension": "py", "version": "3.12" }]

Auth: admin. Language must exist in judge languages.toml.

Auth: admin. 204.


The API says events. The UI says contests. Same thing.

Auth: required. Contests visible to logged-in users.

Auth: required. Event + problems.

POST /v1/public/events/{event_id}/register

Section titled “POST /v1/public/events/{event_id}/register”

Auth: required. Register current user for public contest.

Auth: admin. All events.

Auth: admin

Auth: admin

{
"title": "Spring 2026",
"description": "Internal practice",
"start_time": "2026-04-01T18:00:00Z",
"end_time": "2026-04-01T21:00:00Z",
"teams": false,
"user_id": "admin-uuid"
}

Auth: admin

Auth: admin

MethodPathAuthPurpose
GET/v1/events/{id}/problemsuserProblems in contest
POST/v1/events/{id}/problemsuserAttach problem
GET/v1/events/{id}/submissionsuserFiltered submissions (see below)
GET/v1/events/{id}/attemptsuserICPC-style solve times
GET/v1/events/{id}/participantsuser/adminWho registered
POST/v1/events/{id}/participantsadminAdd participant
GET/POST/v1/events/{id}/questionsuserClarification questions
POST/v1/events/{id}/endadminEnd contest early (sets end_time to now)

Create an event with "teams": true. Team endpoints only work when teams are enabled on that event.

MethodPathAuthPurpose
GET/v1/events/{id}/teamsuserList teams ([{ "id", "event_id", "name" }])
POST/v1/events/{id}/teamsuserCreate a team (creator joins automatically)
GET/v1/events/{id}/teams/meuserCurrent user’s team + members
GET/v1/events/{id}/teams/{team_id}userTeam detail + members
POST/v1/events/{id}/teams/{team_id}/joinuserJoin a team

POST /v1/events/{id}/teams body:

{ "name": "Team Ada" }

201: {"message":"Success","team_id":"uuid"}. Conflicts: 409 duplicate name or already on a team for this event.

POST join body (optional — defaults to the authenticated user):

{ "user_id": "uuid" }

201 on success. One team per user per event.

Auth: required. Query filters:

QueryBehavior
(none)All contest submissions — event owner, judge, or admin only
?user={uuid}That user’s contest submissions — self or admin
?team={uuid}Team submissions — team members (or judge/admin) on team events

Redaction: responses strip source_code, stdout and stderr for submissions you do not own. Status, timing and verdict fields remain visible. Owners, judges and admins see full payloads on their own submissions.


MethodPathPurpose
GET/v1/user/notifications/countUnread count
GET/v1/user/notificationsList
PUT/v1/user/notifications/mark-readMark read

All require auth.


No auth. 200 = up.


{ "error": "Human-readable message", "code": "OPTIONAL_CODE" }
HTTPMeaning
400Bad input
401Missing/invalid auth
403Wrong role
404Not found
409Conflict (e.g. user exists)
500Internal server error

Auth errors are worth memorizing: Malformed JWT token almost always means you prefixed Bearer.