DOJ PUBLIC API · V1
API documentation
Read public DOJ user, problem, contest, and rating data as JSON. Statements and private operational data are not exposed.
https://doj.kr/api/v1Quick start
The current v1 read API does not require an API key and can be called from browsers or servers.
const response = await fetch("https://doj.kr/api/v1/users/dadas08/rating-history?limit=200");
if (!response.ok) throw new Error(`DOJ API: ${response.status}`);
const { points } = await response.json();
const chartData = points.map(({ occurredAt, newRating }) => ({
x: new Date(occurredAt),
y: newRating
}));Common rules
- Response format
- UTF-8 JSON
- CORS
Access-Control-Allow-Origin: *- Time
- UTC ISO 8601 strings
- Rate limit
- Approximately 120 requests per minute per IP
Inspect X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset response headers. Limits may be approximate across multiple server instances.
Private problems and contests, protected contest problem lists, emails, Discord accounts, permissions, and submission source are never returned. Profile images use public delivery URLs; legacy Base64 images may be omitted.
Endpoint overview
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/tags | Algorithm tag catalog |
| GET | /api/v1/users | Search users |
| GET | /api/v1/users/{handle} | Public user profile |
| GET | /api/v1/users/{handle}/contest-participations | Finished contest participations |
| GET | /api/v1/users/{handle}/virtual-participations | Finished category virtual participations |
| GET | /api/v1/users/{handle}/rating-history | Rating history for charts |
| GET | /api/v1/problems | Search public problems |
| GET | /api/v1/problems/{displayId} | Public problem metadata |
| GET | /api/v1/contests | Search visible contests |
| GET | /api/v1/contests/{slug} | Contest metadata |
Users
User handles are case-sensitive across all user API endpoints. Search terms and handles in paths must use the same letter casing as the stored handle.
/api/v1/usersSearch users| Name | Type | Default | Description |
|---|---|---|---|
q | string | "" | Case-sensitive partial handle |
limit | 1..100 | 20 | Results per page |
cursor | string | - | nextCursor from the previous response |
GET /api/v1/users?q=dadas&limit=20
{
"users": [{
"handle": "dadas08",
"rating": 1542,
"contestsCount": 12,
"solvedProblemCount": 84,
"certified": true,
"avatarUrl": "/api/storage-assets/...",
"countryCode": "KR",
"organization": null
}],
"nextCursor": null
}/api/v1/users/{handle}Public user profileAdds bio, account creation time, and follower/following counts to the search fields. Missing or non-public accounts return 404.
/api/v1/users/{handle}/contest-participationsContest participationsReturns finished public and invite-only contests in descending contest-time order. Running and private contests are excluded. Supports limit and cursor.
/api/v1/users/{handle}/virtual-participationsCategory virtual participationsReturns finished virtual participations in public categories. Team records are matched by actual membership. Per-problem scores and submissions are not included.
| Name | Type | Default | Description |
|---|---|---|---|
limit | 1..200 | 100 | History points per page |
cursor | string | - | nextCursor from the previous response |
{
"user": { "handle": "dadas08" },
"participations": [{
"category": {
"path": "algorithm/graphs",
"contents": [
{ "locale": "en", "name": "Graphs" },
{ "locale": "ko", "name": "그래프" }
]
},
"mode": "TEAM",
"team": { "handle": "graph-team" },
"startedAt": "2026-09-12T09:00:00.000Z",
"endedAt": "2026-09-12T11:00:00.000Z",
"totalScore": 420,
"penaltySeconds": 3510
}],
"nextCursor": null
}/api/v1/users/{handle}/rating-historyRating chart dataReturns oldest points first. Use occurredAt for the x-axis and newRating for the y-axis. Changes from private contests preserve numeric continuity while returning contest as null.
| Name | Type | Default | Description |
|---|---|---|---|
limit | 1..200 | 100 | History points per page |
cursor | string | - | nextCursor from the previous response |
{
"user": { "handle": "dadas08" },
"points": [{
"id": "...",
"occurredAt": "2026-07-21T09:00:00.000Z",
"oldRating": 1501,
"newRating": 1542,
"delta": 41,
"kind": "CONTEST",
"contest": {
"slug": "doj-contest-1",
"title": "DOJ Contest 1",
"startAt": "2026-07-21T09:00:00.000Z"
}
}],
"nextCursor": null
}Problems
/api/v1/problemsSearch public problems| Name | Type | Default | Description |
|---|---|---|---|
q | string | "" | Search display ID, slug, or title |
difficultyMin | integer | - | Minimum difficulty value |
difficultyMax | integer | - | Maximum difficulty value |
tags | CSV | "" | Tags that must all match, up to 10 |
sort | enum | display_id_asc | One of the sort values below |
limit | 1..100 | 20 | Results per page |
cursor | string | - | nextCursor from the previous response |
display_id_asc display_id_desc difficulty_asc difficulty_desc solves_desc published_desc
GET /api/v1/problems?tags=dp,graphs&difficultyMin=6&sort=solves_desc
{
"problems": [{
"displayId": 95,
"slug": "95",
"titles": { "ko": "문제 제목", "en": "Problem title" },
"difficulty": 12,
"solveCount": 37,
"timeLimitMs": 2000,
"pythonTimeLimitMs": 4000,
"memoryLimitMb": 512,
"problemType": "STANDARD",
"tags": ["dp", "graphs"],
"authors": ["setter"],
"reviewers": [],
"publishedAt": "2026-08-01T00:00:00.000Z"
}],
"nextCursor": null
}/api/v1/problems/{displayId}Public problem metadataReturns the same metadata for one problem. Statements, I/O descriptions, constraints, samples, editorials, and judge data are excluded.
Contests
/api/v1/contestsSearch contests| Name | Type | Default | Description |
|---|---|---|---|
q | string | "" | Search slug or title |
status | enum | all | all, upcoming, running, or ended |
limit | 1..100 | 20 | Results per page |
cursor | string | - | nextCursor from the previous response |
/api/v1/contests/{slug}Contest metadataReturns timing, window settings, rated state, problem count, description, and rules. Private contests return 404.
Pagination
When nextCursor is not null, pass it unchanged as cursor in the next request. Do not decode or edit it, and do not reuse it after changing filters or sort order.
GET /api/v1/problems?limit=20&cursor=eyJvZmZzZXQiOjIwfQErrors and caching
| HTTP | Error | Meaning |
|---|---|---|
| 400 | invalid_request | Invalid query shape or range |
| 400 | invalid_cursor | Invalid cursor |
| 404 | not_found | Missing or non-public resource |
| 429 | rate_limit_exceeded | Rate limit exceeded |
| 502/503 | api_unavailable | Temporary backend failure |
{ "error": "not_found" }Respect Cache-Control headers and avoid repeatedly requesting identical data. Breaking changes will use a new versioned path.
For the general DOJ guide, see DOJ Guide.