IodidDOCUMENTATION

api/index.mdx

/doc/api

PUBLIC DOCUMENTATION

API — Overview

BaseUrl, ApiResult envelope, LolApiError, ResponseMetadata, validation, CORS and Jackson MixIn — pattern index.

BaseUrl

All LoL APIs expose three prefixes (Spring @RequestMapping, src/main/java/com/safjnest/spring/controller/):

PrefixFile:lineContent
/api/lol/{shard}LolController.java:36profile, matches, rank-history, activity, matchups, livegame, match
/api/lolLolController.java:47, LeaderboardController.java:15, ChampionController.java:16, ProfileIndexableController.java:13, RecordsController.java:16, AiTrainingController.java:20search, leaderboard, champion, records, AI training, indexables
/apiStatusController.java:11global status

{shard} is LeagueShard (no.stelar7.api.r4j.basic.constants.api.regions.LeagueShard) validated by LolApiParameters.requiredShard (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:163) on platform-scoped endpoints. Search is global by default; its optional shard query parameter limits results to one active shard. Active values from LeagueShardUtils.getActives (src/main/java/com/safjnest/lol/utils/LeagueShardUtils.java:64): EUW1, NA1, KR, EUN1, JP1, BR1, LA1, LA2, TR1, RU, OC1, VN2, SG2, TW2, ME1. No authentication — protection only via validation and RiotScheduler.

bash
curl "https://api.safjnest.com/api/lol/search?q=Faker%23EUW"
curl "https://api.safjnest.com/api/lol/search?q=Faker%23EUW&shard=EUW1"
curl "https://api.safjnest.com/api/lol/EUW1/profile/abc-puuid"
curl "https://api.safjnest.com/api/lol/leaderboard?queue=RANKED_SOLO_5X5&page=1&limit=20"
curl "https://api.safjnest.com/api/status"

Envelope — ApiResult and HTTP mapping

Services return ApiResult<T> (src/main/java/com/safjnest/lol/model/ApiResult.java:3):

java
public record ApiResult<T>(Status status, T payload, ResponseMetadata metadata) {
    public enum Status { READY, PARTIAL, PENDING, NOT_FOUND }
}

LolApiResponses

LolApiResponses.from (src/main/java/com/safjnest/spring/controller/LolApiResponses.java:14) maps ApiResult → HTTP:

java
return switch (result.status()) {
    case READY, PARTIAL -> ResponseEntity.ok(result.payload());
    case PENDING -> ResponseEntity.status(HttpStatus.ACCEPTED)
        .body(new LolApiError(HttpStatus.ACCEPTED.value(), pendingCode, pendingMessage, result.metadata()));
    case NOT_FOUND -> throw new ResponseStatusException(HttpStatus.NOT_FOUND, notFoundMessage);
};
ApiResult.StatusHTTPBodyWhen
READY200 OKdirect payloadcomplete and fresh data
PARTIAL200 OKpayload + metadata.refresh=trueProfileService.get stale (src/main/java/com/safjnest/lol/service/ProfileService.java:58) — client shows data but knows a refresh is in progress
PENDING202 ACCEPTEDLolApiError with pending code + metadatawork queued via ComputeScheduler / SyncScheduler
NOT_FOUND404 NOT_FOUNDLolApiError not_foundpuuid/match does not exist

POST /profile/{puuid}/refresh is the only one returning 204 No Content on success (LolController.java:88).

LolApiError — 202 and 4xx errors

LolApiError (src/main/java/com/safjnest/spring/dto/LolApiError.java:5) — sole Spring DTO for errors (ADR-0005/0007):

java
public record LolApiError(int status, String code, String message, ResponseMetadata metadata) {}

Example 202:

json
{
  "status": 202,
  "code": "profile_pending",
  "message": "Profile initialization is pending",
  "metadata": { "lastUpdate": null, "refresh": true, "filter": { "...": "..." } }
}

GET /profile/{puuid} adds header X-Profile-Refresh: true when metadata.refresh is true (LolController.java:294).

Pending code catalog (see reference/errors.mdx):

profile_pending, match_pending, leaderboard_pending, champion_pending, champion_tier_list_pending, champion_data_pending, profile_activity_pending, profile_matchups_pending, profile_records_pending.

ResponseMetadata — pagination / lastUpdate / refresh / filter

ResponseMetadata (src/main/java/com/safjnest/lol/model/ResponseMetadata.java:3):

java
public record ResponseMetadata(Pagination pagination, Long lastUpdate, Boolean refresh, Filter filter) {
    public record Pagination(Integer page, Integer pageSize, Integer limit, Integer offset, Long total, Long pages, Boolean hasMore) {}
}
  • pagination — page/pageSize for leaderboard (LeaderboardController.java:46), limit/offset/total/hasMore for matches (LolController.java:123), pages for leaderboard.
  • lastUpdate — epoch millis of last update (null if pending).
  • refresh — true → client should re-check; also used as header X-Profile-Refresh.
  • filter — Filter that generated the data (echo for debugging).

LolApiParameters — centralized validation

Every parameter goes through LolApiParameters (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:27). Errors → ResponseStatusException(400) via invalid (:235), mapped by LolApiExceptionHandler.

MethodFile:lineRule
requiredShard / region:163 / :155non-blank, valid LeagueShard, UNKNOWN rejected
requiredText:230trim, non-empty — puuid, gameName, tagLine, matchId
rank / requiredRank:37 / :42TierType enum optional/required
queue / optionalQueue / activityQueue:47 / :56 / :52GameQueueType, default TEAM_BUILDER_RANKED_SOLO (:29), ALL→null
rankHistoryQueue:61only RANKED_SOLO_5X5 / RANKED_FLEX_SR (canonical)
role + validateRole:168 / :177LaneType among TOP,JUNGLE,MID,BOT,UTILITY (LaneTypeUtils.playables), rejected if GameQueueTypeUtils.hasLane (:30) is false
patch:103regex \d+\.\d+ — major.minor
page / limit:198 / :203page>=1, leaderboard limit 1..50
matchLimit / matchOffset:210 / :217limit 1..100, offset>=0
matchOrder:222timeStart:asc or timeStart:desc (default desc)
activityFilter:146coherent start/end, champion>=0, factory Filter.canonical() or Filter.summoner()
matchupsFilter:124start/end, queue, patch, role, minGames ≥1
rankHistoryQuery:70queue/view/season/patch/timeStart/timeEnd/sort mutually exclusive, validation via SeasonUtils
otpChampion:183integer >0
recordMetric:193required RecordMetric enum
minGames:119>=1

LolApiResponses — complete mapping

Shown above. LolApiExceptionHandler (src/main/java/com/safjnest/spring/controller/LolApiExceptionHandler.java:23) translates:

ExceptionHTTPcodeFile:line
ResponseStatusException.getStatusCode()codeFor (:89): 400→invalid_request, 404→not_found, 405→method_not_allowed:23
MissingServletRequestParameterException400invalid_request:31
MissingPathVariableException400invalid_request:40
MethodArgumentTypeMismatchException400invalid_request:49
HttpRequestMethodNotSupportedException405method_not_allowed:58
NoHandlerFoundException / NoResourceFoundException404not_found:67
Other500internal_error:76

CORS *

LolApiConfig.addCorsMappings (src/main/java/com/safjnest/spring/config/LolApiConfig.java:42):

java
registry.addMapping("/api/lol/**").allowedOrigins("*").allowedMethods("GET","POST","OPTIONS").allowedHeaders("*");
registry.addMapping("/api/status").allowedOrigins("*").allowedMethods("GET","OPTIONS").allowedHeaders("*");

All browser clients can call without custom preflight.

Jackson MixIn

LolApiConfig.configureMessageConverters (src/main/java/com/safjnest/spring/config/LolApiConfig.java:27):

java
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.addMixIn(ChampionStatistics.class, FilterApiMixin.class);
mapper.addMixIn(Build.class, FilterApiMixin.class);
mapper.addMixIn(Filter.class, FilterApiVisibilityMixin.class);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
  • FilterApiMixin (:36): @JsonIgnoreProperties("filter") on ChampionStatistics and Build — the internal filter field is not serialized in response JSON.
  • FilterApiVisibilityMixin (:39): @JsonAutoDetect(fieldVisibility = ANY) on Filter — ensures field serialization even when Filter is included in ResponseMetadata.

Shared codec for Mongo/Redis: JsonCodec (src/main/java/com/safjnest/utils/JsonCodec.java:17) with FIELD ANY, GETTER NONE, JavaTimeModule, WRITE_DATES_AS_TIMESTAMPS=false.

Pattern index — all routes

/api/lol/{shard} — LolController (LolController.java:36)

#MethodPatternFile:lineDescription
2GET/api/lol/{shard}/profile/{puuid}:63full profile SummonerView (200/202/404)
3GET/api/lol/{shard}/profile-by-name/{gameName}/{tagLine}:226profile by Riot ID
4POST/api/lol/{shard}/profile/{puuid}/refresh:75request refresh — 204, cooldown 2m
5GET/api/lol/{shard}/profile/{puuid}/matches:91paginated match history
6GET/api/lol/{shard}/profile/{puuid}/rank-history:130rank progress history
7GET/api/lol/{shard}/profile/{puuid}/activity:156aggregated activity (202 if computing)
8GET/api/lol/{shard}/profile/{puuid}/matchups:184matchups by opponent
9GET/api/lol/{shard}/profile/{puuid}/records:209profile records
10GET/api/lol/{shard}/livegame/{puuid}:240live game by puuid
11GET/api/lol/{shard}/livegame-by-name/{gameName}/{tagLine}:253live game by Riot ID
12GET/api/lol/{shard}/match/{gameId}:268match detail (202 if syncing)

/api/lol — global

#MethodPatternController:line
1GET/api/lol/search?q=&shard=LolController.java:47
13GET/api/lol/leaderboardLeaderboardController.java:24
14GET/api/lol/leaderboard/rank-distribution:60
15GET/api/lol/leaderboard/top-regions:71
16GET/api/lol/champion/indexablesChampionController.java:25
17GET/api/lol/champions/tier-list:30
18GET/api/lol/champion/{champion}:51
19GET/api/lol/recordsRecordsController.java:25
20GET/api/lol/records/{metric}:32
21GET/api/lol/profile/indexablesProfileIndexableController.java:18
22GET/api/lol/ai/trainingAiTrainingController.java:23

/api — system

#MethodPatternController:line
23GET/api/statusStatusController.java:20

Note: the spec says "17 patterns" — the table actually lists 23. The 17 canonical documents are 1–12 + 13–17; the remaining (records, profile/indexables, ai/training, status, livegame-by-name, profile/records) bring the total to 23.

Quick examples

bash
# Search across every shard, or limit it with `shard`
curl "https://api.safjnest.com/api/lol/search?q=Hide%20on%20bush%23KR1"
curl "https://api.safjnest.com/api/lol/search?q=Hide%20on%20bush%23KR1&shard=EUW1"

# Profile (handle 202)
curl -i "https://api.safjnest.com/api/lol/EUW1/profile/abc-123-puuid"

# Leaderboard
curl "https://api.safjnest.com/api/lol/leaderboard?queue=RANKED_SOLO_5X5&rank=MASTER&page=1&limit=20"

# Champion tier list
curl "https://api.safjnest.com/api/lol/champions/tier-list?patch=15.6&rank=EMERALD&region=EUW1"

# Records
curl "https://api.safjnest.com/api/lol/records/KILLS?region=EUW1&limit=20&offset=0"

# Status
curl "https://api.safjnest.com/api/status"