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.

Contest organizer workflows: Run a contest.

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/status -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_resetnoneIssue one-time reset token (no email sent today)
POST/v1/basic_reset_passwordnoneSet new password with email, token, and 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

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 ELASTIC_ENABLED=true).

Auth: required

Hidden test cases are always redacted for non-judge callers (empty input/output, hidden: true). There is no ?type=private query parameter. Judges and admins fetch full tests via GET /v1/problem_description/{id}/tests.

{
"id": 1,
"title": "Reverse String",
"identifier": "reverse-string",
"prompt": "...",
"difficulty": "EASY",
"public": true,
"accept_timeout": 10.0,
"execution_timeout": 5.0,
"memory_limit": 256,
"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
}

timeout is a legacy default when the specific timeout fields are omitted.

Validation (400):

  • title, identifier, prompt non-empty (after trim)
  • difficulty — one of VERY EASY, EASY, MEDIUM, HARD, VERY HARD (space in VERY EASY / VERY HARD)
  • At least one test case with non-empty input and expected_output

201: { "id": 1, "event_problem_id": 0 }

Auth: admin. Partial updates. Same validation when fields included. 200 same shape as POST response.

DELETE /v1/problems/{problem_description_id}

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

Auth: admin. 204.

PUT /v1/admin/problems/{problem_id}/toggle-visibility

Section titled “PUT /v1/admin/problems/{problem_id}/toggle-visibility”

Auth: admin. No body. Flips public boolean. 200 with updated problem summary.

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

Auth: required

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

Auth: required. Categories for a problem description ID.


Enqueue for grading. Auth: required

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

event_id optional — required for contest submissions during the active window.

201:

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

Auth: owner, judge, or admin. Full record including test_case_results after grading.

GET /v1/submissions/{submission_id}/status

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

Auth: owner, judge, or admin. Lightweight poll:

{ "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


Auth: required.

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

201 body is a plain UUID string (not JSON-wrapped).

Unauthenticated demo routes (IP rate-limited 5/min, burst 2):

  • POST /v1/public/input_submissions
  • POST /v1/bench/input_submissions

Authenticated rate limits (in-memory per data-layer instance):

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

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

Auth: required. While running:

{ "status": "PENDING" }

When finished:

{ "status": "ACCEPTED", "stdout": "...", "stderr": "", "finished": true, "runtime": 0.042 }

GET /v1/public/input_submissions/{id} / GET /v1/bench/input_submissions/{id}

Section titled “GET /v1/public/input_submissions/{id} / GET /v1/bench/input_submissions/{id}”

Auth: none. Same poll shapes as authenticated GET.

PATCH /v1/input_submissions/{submission_id}

Section titled “PATCH /v1/input_submissions/{submission_id}”

Auth: judge or admin. Judge worker callback for custom runs.

{ "status": "ACCEPTED", "stdout": "", "stderr": "", "runtime": 0.042 }

204 on success.


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.

Scoring rules: Run a contest — standings.

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

Auth: required. Event detail with participant list and problem id refs.

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

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

Auth: required. Self-register. 409 if already registered.

GET /v1/public/events/{event_id}/participants

Section titled “GET /v1/public/events/{event_id}/participants”

Auth: required. Array of User objects.

Auth: admin. All events.

Auth: admin

Auth: admin. Lookup by title with full problem payloads and languages.

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",
"problems": [{
"problem_id": 1,
"accept_timeout": 5.0,
"execution_timeout": 5.0,
"memory_limit": 256
}],
"languages": [1]
}

Auth: admin. Updates title, description, times, teams — not problem attachments.

Auth: admin

MethodPathAuthPurpose
GET/v1/events/{id}/problemsuserProblems in contest
GET/v1/events/{id}/problems/{event_problem_id}userSingle contest problem + public tests
POST/v1/events/{id}/problemsuserAttach problem { "problem_id": 1 }201
GET/v1/events/{id}/submissionsuserFiltered submissions (see below)
GET/v1/events/{id}/attemptsuserICPC attempt stats per user/problem
GET/v1/events/{id}/user_problem_statususerCurrent user’s per-problem status
GET/v1/events/{id}/problems_statsuserAcceptance counts per problem
GET/v1/events/{id}/participantsadminParticipant list
POST/v1/events/{id}/participantsadminAdd { "user_id": "..." }
GET/v1/events/{id}/questionsuserList clarifications
POST/v1/events/{id}/questionsuserAsk { "question": "...", "problem_id": 1 }
PUT/v1/events/{id}/questions/{question_id}/answeruserAnswer { "answer": "..." }
POST/v1/events/{id}/endadminEnd early (end_time = now)

ICPC-style stats per (user_id, problem_id):

[{
"user_id": "uuid",
"problem_id": 1,
"attempts": 3,
"total_attempts": 5,
"first_accepted_time": "2026-04-01T19:30:00Z",
"minutes_to_solve": 45
}]

attempts counts submissions that affect scoring; see Run a contest.

GET returns EventQuestionExt array with nested user, optional problem, answerer.

POST 201 with created question. Notifies other participants (notification_type: "question").

PUT answer 200 {"message":"question answered successfully"}. Notifies question author (notification_type: "answer"). Any authenticated user can call this endpoint — restrict at the proxy if needed.

Create with "teams": true.

MethodPathAuthPurpose
GET/v1/events/{id}/teamsuserList teams
POST/v1/events/{id}/teamsuserCreate { "name": "Team Ada" }
GET/v1/events/{id}/teams/meuserCurrent user’s team + members
GET/v1/events/{id}/teams/{team_id}userTeam detail
POST/v1/events/{id}/teams/{team_id}/joinuserJoin (optional { "user_id" })

One team per user per event. Standings remain per user.

Query filters:

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

Redaction: strips source_code, stdout, stderr for submissions you don’t own.


Triggered by contest clarifications. All require auth.

{ "count": 3 }

Returns recent notifications (unread + read from last 24h):

[{
"id": "uuid",
"user_id": "uuid",
"event_id": 1,
"question_id": "uuid",
"notification_type": "question",
"is_read": false,
"created_at": "...",
"question": { }
}]

notification_type: "question" | "answer".

Empty body. 200 {"message":"notifications marked as read"}.


MethodPathResponse
GET/{"status":"ok","service":"nextjudge-data-layer","health":"/health","api":"/v1"}
GET/health{"status":"ok"}
GET/healthy{"status":"ok"}

No auth.


{ "error": "Human-readable message", "code": "OPTIONAL_CODE" }

Some validation errors use { "code": "400", "message": "..." }.

HTTPMeaning
400Bad input
401Missing/invalid auth
403Wrong role
404Not found
409Conflict
429Rate limited
500Internal server error

Malformed JWT token almost always means you prefixed Bearer.