IodidDOCUMENTATION

getting-started.mdx

/doc/getting-started

PUBLIC DOCUMENTATION

Getting Started

How to call Iodid APIs — baseUrl, validation, errors, pagination and filters.

BaseUrl and shard

LoL APIs are under:

text
https://api.safjnest.com/api/lol

Most platform-scoped endpoints add /{shard}, validated by LolApiParameters.requiredShard (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:163). Search uses /search?q=... across all active shards by default; add &shard=EUW1 to limit it to one shard. Active values defined in 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

Examples: GET /api/lol/search?q=Faker%23EUW searches all shards; GET /api/lol/search?q=Faker%23EUW&shard=EUW1 limits the search (src/main/java/com/safjnest/spring/controller/LolController.java:47).

bash
curl "https://api.safjnest.com/api/lol/search?q=Player%23EUW"
curl "https://api.safjnest.com/api/lol/search?q=Player%23EUW&shard=EUW1"

No authentication required. The APIs are public; protection is only via parameter validation and internal rate limiting via RiotScheduler.

Parameter validation — LolApiParameters

Every query/path parameter goes through LolApiParameters (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:27):

MethodFile:lineRule
requiredShard:163shard non-empty, must be a valid LeagueShard, UNKNOWN rejected
requiredText:230trim, non-empty — used for puuid, gameName, tagLine, matchId
rank / requiredRank:37, :42TierType enum, rank optional, requiredRank required
queue / optionalQueue:47, :56GameQueueType, default TEAM_BUILDER_RANKED_SOLO (:29), ALL equals null
role + validateRole:168, :177LaneType among TOP,JUNGLE,MID,BOT,UTILITY (LaneTypeUtils.playables), rejected if queue has no lanes (GameQueueTypeUtils.hasLane :30)
patch:103regex \d+\.\d+ — major.minor form
page / limit / matchLimit / matchOffset:198, :203, :210, :217page >=1, leaderboard limit 1..50, match limit 1..100, offset >=0
matchOrder:222timeStart:asc or timeStart:desc (default desc)
activityFilter:146coherent start/end period, champion >=0, uses Filter.canonical() or Filter.summoner()
otpChampion:183integer >0
recordMetric:193required RecordMetric enum

Validation errors throw ResponseStatusException with 400 BAD_REQUEST via invalid (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:235) and are mapped by LolApiExceptionHandler.

Error envelope — LolApiError

java
// src/main/java/com/safjnest/spring/dto/LolApiError.java:5
public record LolApiError(int status, String code, String message, ResponseMetadata metadata) {}

LolApiError is the sole Spring DTO for errors (ADR-0005/0007). Fields:

  • status — numeric HTTP status
  • code — application code (e.g. profile_pending, match_pending, profile_activity_pending)
  • message — human-readable description
  • metadata — optional ResponseMetadata with pagination, lastUpdate, refresh, filter (src/main/java/com/safjnest/lol/model/ResponseMetadata.java:3)

LolApiExceptionHandler (src/main/java/com/safjnest/spring/controller/LolApiExceptionHandler.java:23) translates ResponseStatusException, MissingServletRequestParameterException, MethodArgumentTypeMismatchException into LolApiError JSON.

ApiResult — 200 / 202 / 404

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

java
public enum Status { READY, PARTIAL, PENDING, NOT_FOUND }

HTTP mapping in LolApiResponses.from (src/main/java/com/safjnest/spring/controller/LolApiResponses.java:14):

StatusHTTPBody
READY / PARTIAL200 OKdirect payload (e.g. SummonerView, ProfileActivity)
PENDING202 ACCEPTEDLolApiError with pending code + metadata.refresh=true
NOT_FOUND404 NOT_FOUNDResponseStatusException with message

Example pending response (GET /api/lol/EUW1/profile/{puuid} when not yet ready — src/main/java/com/safjnest/spring/controller/LolController.java:291):

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

The profile also includes header X-Profile-Refresh: true when metadata.refresh is true (src/main/java/com/safjnest/spring/controller/LolController.java:294).

ResponseMetadata (src/main/java/com/safjnest/lol/model/ResponseMetadata.java:22) contains Pagination (page, pageSize, limit, offset, total, pages, hasMore) when applicable.

Pagination

Two styles, both with LolApiParameters validation:

Leaderboard — page + limit (LeaderboardController.java:40):

bash
curl "https://api.safjnest.com/api/lol/EUW1/leaderboard?queue=RANKED_SOLO_5X5&rank=DIAMOND&page=2&limit=20"
# limit 1..50, page >=1

Response LeaderboardPage with ResponseMetadata.pagination (src/main/java/com/safjnest/lol/model/ResponseMetadata.java:22).

Match history — offset + limit + sort (LolController.java:91):

bash
curl "https://api.safjnest.com/api/lol/EUW1/profile/puuid/matches?limit=20&offset=0&sort=timeStart:desc&queue=RANKED_SOLO_5X5"
# limit 1..100, offset >=0, sort timeStart:asc|desc

The controller builds MatchPage with hasMore (src/main/java/com/safjnest/lol/service/MatchService.java:244) and ResponseMetadata with pagination (src/main/java/com/safjnest/spring/controller/LolController.java:123).

Canonical filter

Filter (src/main/java/com/safjnest/lol/model/Filter.java:13) represents the combination champion, lane, queue, rank, patch, region, opponent, duo, timeStart, timeEnd.

  • Filter.canonical() (src/main/java/com/safjnest/lol/model/Filter.java:44) — current-season filter, zeroes champion/lane/queue/rank/patch/region, period = SeasonUtils.getCurrentSeasonRange() (src/main/java/com/safjnest/lol/utils/SeasonUtils.java:41).
  • Filter.summoner(timeStart, timeEnd) (src/main/java/com/safjnest/lol/model/Filter.java:31) — filter for profile/activity, explicit period, other fields zeroed.
  • Filter.activityFilter(start, end, queue, champion) (LolApiParameters.java:146) — validated factory for GET /profile/{puuid}/activity.

Filters are serialized as URL-safe base64 keys toSummonerKey() (src/main/java/com/safjnest/lol/model/Filter.java:283) via FilterCodec.encodeUrl (src/main/java/com/safjnest/lol/utils/FilterCodec.java:15). Used as Redis key for statistics, activity, matchups (ProfileService.java:341).

Complete examples

curl

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 by puuid
curl "https://api.safjnest.com/api/lol/EUW1/profile/abc-123-puuid"

# Profile by Riot ID
curl "https://api.safjnest.com/api/lol/EUW1/profile-by-name/Hide%20on%20bush/KR1"

# Match detail (202 if queued)
curl "https://api.safjnest.com/api/lol/EUW1/match/EUW1_1234567890"

# Activity with time filter
curl "https://api.safjnest.com/api/lol/EUW1/profile/puuid/activity?start=0&end=0&queue=RANKED_SOLO_5X5&champion=0"

# Matchups
curl "https://api.safjnest.com/api/lol/EUW1/profile/puuid/matchups?queue=RANKED_SOLO_5X5&role=MID&minGames=5"

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

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

JavaScript (fetch)

js
const baseUrl = "https://api.safjnest.com/api/lol";

async function getProfile(shard, puuid) {
  const res = await fetch(`${baseUrl}/${shard}/profile/${puuid}`);
  if (res.status === 202) {
    const err = await res.json(); // LolApiError
    console.log("pending:", err.code, err.metadata);
    return { pending: true, ...err };
  }
  if (res.status === 404) throw new Error("Profile not found");
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const view = await res.json(); // SummonerView
  const needsRefresh = res.headers.get("X-Profile-Refresh") === "true";
  return { pending: false, view, needsRefresh };
}

async function search(query, shard) {
  const params = new URLSearchParams({ q: query, ...(shard ? { shard } : {}) });
  const res = await fetch(`${baseUrl}/search?${params}`);
  if (!res.ok) throw new Error(await res.text());
  return res.json(); // List<SummonerView>
}

async function getMatches(shard, puuid, { limit = 20, offset = 0, sort = "timeStart:desc" } = {}) {
  const url = `${baseUrl}/${shard}/profile/${puuid}/matches?limit=${limit}&offset=${offset}&sort=${sort}`;
  const res = await fetch(url);
  return res.json(); // MatchPage with metadata.pagination
}

Always handle 202 as "data being prepared" — retry after a short interval or show metadata.refresh state.