3.2 KiB
ESI operations
The server ESI adapter uses the fixed trusted base https://esi.evetech.net and pins
X-Compatibility-Date: 2026-08-18 (ESI_COMPATIBILITY_DATE). It exposes only the
character asset GET and global market-price GET needed by the application; callers
cannot provide an arbitrary ESI URL.
Asset synchronization
EsiClient.getAssets(characterId, accessToken, authorizationKey) fetches page one,
reads X-Pages, fetches all remaining pages, de-duplicates by item_id while
checking for duplicates, and revalidates page one after the other pages. A changed
page count, duplicate ID, malformed page, failed request, or changed first page
fails the operation. The caller must not persist a returned partial result: the
method returns only a complete { assets, syncedAt, expiresAt } snapshot. Asset
IDs are retained as decimal strings, and the response is parsed with
lossless-json so an int64 cannot be rounded by JavaScript.
This is a stability check, not a transaction. ESI's paginated endpoint does not
promise a true multi-page snapshot while its cache expires between page requests.
A caller should stage pages and replace the persisted Convex snapshot only after
getAssets resolves. Convex persistence is intentionally outside this adapter.
The cache is private to an EsiClient and keyed by user/authorization context,
character, and scopes. authorizationKey should therefore identify the linked
application user and include the character/scopes when available:
{ userId, characterId, scopes }
A string key is supported for callers that have no scope object, but is treated as an isolated user key. Tokens are never used as cache keys or emitted in metrics.
Responses retain their raw text, ETag, Last-Modified, and cache metadata. Fresh
responses use the server TTL; stale responses send If-None-Match and
If-Modified-Since, and a 304 reuses the cached body. Cache-Control, Expires,
Date, and Age are honored. no-store responses retain neither body nor
validators. Requests have one deadline covering response-body reads and bounded
retries for transient network/5xx/420/429 responses. Retry-After (seconds or
HTTP date) and the legacy ESI error-limit reset are respected. Exhausted new
limiter buckets are cooled down per authorization context and group; the legacy
error limiter is cooled down globally. A server delay that exceeds the remaining
request budget returns a typed rate-limit error rather than retrying early. 401
and 403 are never blindly retried.
Market estimates
EsiMarketEstimates reads public GET /markets/prices. It uses only
average_price; a missing average price is omitted and adjusted_price is never
a fallback. The returned PriceSnapshot.source includes the pinned compatibility
date, asOf is the retrieval time (ESI supplies no source timestamp), and prices
are integer ISK cents represented as validated decimal strings. The route is a
cached global reference estimate, not a regional or structure order book.
Metrics supplied through EsiClient's observe callback include endpoint, status,
retry/cache state, and redacted rate-limit headers. They do not include access
tokens, authorization keys, or response bodies.