API Reference
All endpoints below use this prefix:
/api/public/v1
Auth header for every call:
Authorization: Bearer <token>
Example environment used across commands:
export BASE_URL="https://highlights-api.rankacy.com"
export TOKEN="rk_live_..."
Pagination pattern
List endpoints use:
limit(bounded per endpoint)offset
Response format:
{
"items": [...],
"pagination": {
"total": 120,
"limit": 20,
"offset": 40
}
}
Demos endpoints
| Method | Path | Description |
|---|---|---|
POST |
/demos/upload |
Upload .dem file |
GET |
/demos |
List demos |
GET |
/demos/{demo_id} |
Demo detail |
GET |
/demos/{demo_id}/kills |
Parsed kill feed |
GET |
/demos/{demo_id}/players |
Parsed players (SteamID64 strings) |
POST /demos/upload
Request: multipart form-data
file(required):.demgenerate_auto_highlight(optional, boolean, defaulttrue): controls whether automatic TOP5 highlight generation is enabled for this user-demo linkresolution_id(optional, integer): must be sent together withfps_idto enable upload-time auto highlight settingsfps_id(optional, integer): must be sent together withresolution_idto enable upload-time auto highlight settings- Automatic TOP5 highlights created by upload processing always persist
show_tick=false
curl -X POST "$BASE_URL/api/public/v1/demos/upload" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@$DEMO_FILE" \
-F "generate_auto_highlight=true" \
-F "resolution_id=1" \
-F "fps_id=1"
Response 202:
{
"demo_id": 52,
"status": "queued",
"auto_highlight_requested": false,
"was_already_processed_successfully": false,
"user_demo_assignment": "newly_assigned",
"auto_highlight_skip_reason": null,
"auto_highlight_skip_message": null,
"auto_highlight_estimated_credit": 0.1,
"auto_highlight_current_credit": 20.0
}
Upload response fields:
status:queuedfor newly created demo; otherwise existing demo status (new|processing|success|failed)auto_highlight_requested:truewhen this request queued an automatic TOP5 highlight (requiresgenerate_auto_highlight=true,resolution_id+fps_id, enough credit, demo alreadySUCCESS, and no existing highlight for this user+demo)was_already_processed_successfully:truewhen this upload matched an existing demo that was already inSUCCESSuser_demo_assignment:newly_assignedwhen this call created the user<->demo link,already_assignedwhen link already existedauto_highlight_skip_reason/auto_highlight_skip_message: populated when upload-time auto highlight is skipped (auto_highlight_disabled,missing_generation_settings,partial_generation_settings,insufficient_credit,existing_highlight,no_kills)auto_highlight_estimated_credit/auto_highlight_current_credit: returned when render options were resolved so the client can compare cost vs balance
GET /demos
Query params:
created_from(ISO datetime)created_to(ISO datetime)mapstatus(NEW|PROCESSING|SUCCESS|FAILED)upload_type(MANUAL|API)searchlimit(1..100)offset(>=0)
curl -G "$BASE_URL/api/public/v1/demos" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "map=mirage" \
--data-urlencode "status=SUCCESS" \
--data-urlencode "limit=20" \
--data-urlencode "offset=0"
GET /demos/{demo_id}
curl "$BASE_URL/api/public/v1/demos/$DEMO_ID" \
-H "Authorization: Bearer $TOKEN"
GET /demos/{demo_id}/kills
Query params:
roundattacker_steam_id(SteamID64 string)victim_steam_id(SteamID64 string)limit(1..500)offset
Response note:
attacker_steam_idandvictim_steam_idare returned as strings (BIGINT-safe serialization).
curl -G "$BASE_URL/api/public/v1/demos/$DEMO_ID/kills" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "attacker_steam_id=76561198000000000" \
--data-urlencode "round=8" \
--data-urlencode "limit=50"
Response shape:
{
"items": [
{
"demo_kill_id": 801,
"demo_id": 52,
"tick": 15423,
"round": 8,
"attacker_steam_id": "76561198000000000",
"victim_steam_id": "76561198000000001",
"weapon": "ak47",
"item_id": 7,
"is_headshot": true,
"score": 9.5
}
],
"pagination": {"total": 1, "limit": 50, "offset": 0}
}
GET /demos/{demo_id}/players
Response note:
steam_idis returned as a string (BIGINT-safe serialization).
curl "$BASE_URL/api/public/v1/demos/$DEMO_ID/players" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"items": [
{
"id": 901,
"player_name": "rankacy_pro",
"steam_id": "76561198000000000"
},
{
"id": 902,
"player_name": "opponent_one",
"steam_id": "76561198000000001"
}
]
}
Highlights endpoints
| Method | Path | Description |
|---|---|---|
GET |
/highlights |
List highlights |
GET |
/highlights/{highlight_id} |
Highlight detail |
DELETE |
/highlights/{highlight_id} |
Delete highlight |
GET |
/highlights/resolutions |
Resolution options |
GET |
/highlights/fps |
FPS options |
GET |
/highlights/cost |
Cost estimate for resolution + FPS |
POST |
/highlights |
Generate highlight (auto) |
POST |
/highlights/by-ticks |
Generate by tick ranges |
POST |
/highlights/by-kill |
Generate by kill IDs |
GET /highlights
Query params:
demo_idstatus(NEW|PROCESSING|SUCCESS|FAILED)created_fromcreated_totype(auto|ticks|kill)limit(1..100)offset
curl -G "$BASE_URL/api/public/v1/highlights" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "type=auto" \
--data-urlencode "limit=20"
GET /highlights/{highlight_id}
curl "$BASE_URL/api/public/v1/highlights/$HIGHLIGHT_ID" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"id": 311,
"demo_id": 52,
"status": "SUCCESS",
"title": "Auto highlight for demo 52",
"type": "AUTO",
"resolution_id": 1,
"fps_id": 1,
"cost": 0.1,
"score": 18.75,
"length": 34.2,
"size": 26391542,
"use_transition": false,
"show_tick": false,
"intro": "NONE",
"created_at": "12-02-2026 12:20:19",
"image_url": "https://cdn.example.com/highlights/311.jpg",
"video_url": "https://cdn.example.com/highlights/311.mp4",
"containers": [
{
"id": 901,
"start_tick": 15423,
"end_tick": 15610,
"speed": 1,
"steam_id": "76561198000000000",
"player_name": "rankacy_pro",
"kills": [
{
"demo_kill_id": 801,
"tick": 15440,
"round": 8,
"attacker_steam_id": "76561198000000000",
"attacker_name": "rankacy_pro",
"victim_steam_id": "76561198000000001",
"victim_name": "opponent_one",
"weapon": "ak47",
"item_id": 7,
"is_headshot": true,
"score": 9.5
}
]
}
]
}
DELETE /highlights/{highlight_id}
curl -X DELETE "$BASE_URL/api/public/v1/highlights/$HIGHLIGHT_ID" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"highlight_id": 311,
"status": "deleted"
}
GET /highlights/resolutions
curl "$BASE_URL/api/public/v1/highlights/resolutions" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"items": [
{"id": 1, "name": "1280x720", "width": 1280, "height": 720}
]
}
GET /highlights/fps
curl "$BASE_URL/api/public/v1/highlights/fps" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"items": [
{"id": 1, "name": "60 FPS", "fps": 60}
]
}
GET /highlights/cost
Query params:
resolution_id(required)fps_id(required)
curl -G "$BASE_URL/api/public/v1/highlights/cost" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "resolution_id=1" \
--data-urlencode "fps_id=1"
Response shape:
{
"resolution_id": 1,
"fps_id": 1,
"resolution_credit": 0.1,
"fps_multiplier": 1.0,
"cost": 0.1
}
POST /highlights
Accepted optional fields:
titlemode(accepted compatibility field; currently ignored by the backend)use_transitionshow_tickintro(NONE|GENERIC|SCENIC)
curl -X POST "$BASE_URL/api/public/v1/highlights" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"demo_id": 52,
"resolution_id": 1,
"fps_id": 1,
"title": "Mirage recap",
"show_tick": true,
"use_transition": false,
"intro": "NONE"
}'
POST /highlights/by-ticks
Each tick segment must include steam_id (SteamID64 string) so the clip is rendered from that player perspective. Each segment may also include speed (0.25|0.5|1|2, default 1).
curl -X POST "$BASE_URL/api/public/v1/highlights/by-ticks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"demo_id": 52,
"ticks": [
{"start_tick": 15423, "end_tick": 15610, "speed": 1, "steam_id": "76561198000000000"},
{"start_tick": 30120, "end_tick": 30400, "speed": 0.5, "steam_id": "76561198000000005"}
],
"resolution_id": 1,
"fps_id": 1,
"title": "Round pivots",
"show_tick": true
}'
POST /highlights/by-kill
Accepts kill identifiers in these shapes:
demo_kill_ids: [1,2,3](recommended)kill_ids: [1,2,3](alias)demo_kill_id: 1(single-item alias)speed(0.25|0.5|1|2, default1) applies to every generated segment
curl -X POST "$BASE_URL/api/public/v1/highlights/by-kill" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"demo_id": 52,
"demo_kill_ids": [801, 805],
"pre_ticks": 192,
"post_ticks": 192,
"speed": 2,
"resolution_id": 1,
"fps_id": 1,
"title": "Entry chain",
"show_tick": true
}'
Response for generation endpoints (202):
{
"highlight_id": 311,
"status": "queued"
}
Account endpoints
| Method | Path | Description |
|---|---|---|
GET |
/me/credit |
Current balance and profile credit totals |
GET |
/me/transactions |
Highlight transaction history |
GET /me/credit
curl "$BASE_URL/api/public/v1/me/credit" \
-H "Authorization: Bearer $TOKEN"
Response shape:
{
"user_id": 7,
"email": "dev@example.com",
"is_staff": false,
"credit": 13.5,
"credit_bought": 20.0,
"credit_given": 3.5,
"created_at": "12-02-2026 12:20:19"
}
GET /me/transactions
Query params:
from(ISO datetime)to(ISO datetime)limit(1..100)offset(>=0)
curl -G "$BASE_URL/api/public/v1/me/transactions" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "limit=20" \
--data-urlencode "offset=0"
Response shape:
{
"items": [
{
"id": 14,
"highlight_id": 311,
"demo_id": 52,
"resolution_id": 1,
"fps_id": 1,
"highlight_status": "NEW",
"credit": 0.1,
"created_at": "12-02-2026 12:20:19"
}
],
"pagination": {"total": 1, "limit": 20, "offset": 0}
}
Error model
Standard errors:
401auth missing/invalid403revoked token or forbidden resource404resource missing422request validation/domain validation
Examples:
{"detail": "API token missing"}
{"detail": "API token revoked"}
{"detail": "Demo not found"}
{"detail": "Tick range is outside demo bounds"}
{
"detail": [
{
"loc": ["body", "resolution_id"],
"msg": "Field required",
"type": "missing"
}
]
}
Rate limits
No hard per-token rate-limiting policy is currently enforced by these routes.
Recommended client behavior:
- use retries with exponential backoff for
5xxand network errors - avoid aggressive polling (prefer webhooks where possible)
- use reasonable pagination (
limit <= 100) - deduplicate retries client-side by your own request IDs