Skip to content

ESI.ts logo

ESI.ts ​

npm versionLicense: GPL v3TypeScriptCI/CD PipelineCoverageMutation scoreTestsEARS requirementsnpm downloadsOpenSSF Scorecard

A TypeScript client for the EVE Online ESI API, built as an engineered product rather than a generated wrapper. It covers every operation in the ESI OpenAPI specification. Every response is validated at runtime, and caching, rate limiting, retry and pagination follow the rules ESI actually enforces. The claims on this page are backed by a test suite that is itself tested.

Documentation site: lgriffin.github.io/ESI.ts, with these guides, a page for every runnable example, and the API reference generated from the TSDoc. The requirements dashboard lists every EARS requirement the client is held to, the scenarios that prove it and its verdict, and how to write a new one.

Release line. Version 12.3.0 is current on npm. 12.0 corrects five list methods and their detail schemas to the shapes ESI actually sends, which changes their return types; nothing else breaks. The 10.x line stays available for Node 18 and 20. What 12.0 changes · What 11.0 changed · Changelog

Install ​

bash
npm install @lgriffin/esi.ts

You need Node.js 22.12 or later; on Node 18 or 20, stay on 10.x. TypeScript projects need TypeScript 5.4 or later, under node16, nodenext or bundler module resolution. The consumer contract checks every one of those combinations against the published tarball, as ES module and CommonJS.

Quick start ​

typescript
import { EsiClient, createConsoleLogger } from '@lgriffin/esi.ts';

const log = createConsoleLogger('info');
const client = new EsiClient({
  userAgent: 'my-app/1.0 (you@example.com)',
  logger: createConsoleLogger(), // library diagnostics, at ESI_LOG_LEVEL (default warn)
});
try {
  const status = await client.status.getStatus();
  log.info(`${status.players} pilots online`);
} finally {
  client.shutdown();
}

The examples here and in the guides log through an ILogger rather than console: log is the program's own output, and the client gets a logger of its own for library diagnostics. Logging has the details and how to plug in pino or anything else.

Public data needs no token. For character data, pass an EVE SSO access token, or set ESI_ACCESS_TOKEN, and the client attaches it only to the calls that declare a scope:

typescript
const character = await client.characters.getCharacterPublicInfo(characterId);
const prices = await client.market.getMarketPrices();

const authed = new EsiClient({ accessToken: token });
const wallet = await authed.wallet.getCharacterWallet(characterId);

Next: Using the client for configuration and every domain client, and Authentication for SSO, refresh and many characters.

Multiple characters ​

An application that acts for many characters has one relationship with ESI, so @lgriffin/esi.ts/client builds one runtime and a view per identity. The views share the rate limiter, the error budget and the ETag cache; each identity's authenticated entries stay apart. esi.public is typed so that an authenticated call does not compile.

typescript
import { createEsi } from '@lgriffin/esi.ts/client';

const esi = createEsi({ userAgent: 'my-app/1.0 (you@example.com)' });
const status = await esi.public.status.get();
const wallet = await esi
  .as(tokens.identity(characterId))
  .character(characterId)
  .wallet.get();

Many characters has the identities (EsiTokenManager, a raw token, a TokenProvider), what the views share, and the move from tokens.createClient.

Testing your application without ESI ​

createMockTransport() from @lgriffin/esi.ts/testing answers requests from a table of routes and records what your code sent. Everything between your call and the transport is the real pipeline, so this runs as written:

typescript
import { createEsi, identityFromToken } from '@lgriffin/esi.ts/client';
import { createMockTransport } from '@lgriffin/esi.ts/testing';
import { createConsoleLogger } from '@lgriffin/esi.ts';

const log = createConsoleLogger('info');

const transport = createMockTransport().respond({
  method: 'GET',
  path: '/characters/{character_id}/wallet',
  body: 1234567.89,
});
const esi = createEsi({
  userAgent: 'my-app/1.0 (you@example.com)',
  transport,
});
try {
  const view = esi.as(identityFromToken('an-access-token'));
  const wallet = await view.character(2114794365).wallet.get();
  log.info(`Wallet: ${wallet}`); // Wallet: 1234567.89
  log.info(`Sent: ${transport.sent[0]?.headers['authorization']}`); // Sent: Bearer an-access-token
} finally {
  esi.shutdown();
}

A request no route answers is rejected with an EsiConfigurationError naming the request, without a retry, and appears in transport.unrouted. Testing has the route options and the record.

What you get ​

CapabilityWhat ESI.ts does
Full coverage39 domain clients and 235 routes. They cover all 235 operations in the ESI specification at compatibility date 2026-08-18, plus the specification documents themselves. spec:coverage fails the build if one is missing.
Runtime validationEvery GET response is checked against a hand-written Zod schema. Unknown fields pass through, so an additive change from CCP never breaks you. A changed shape throws EsiValidationError instead of corrupting your data quietly.
CachingA GET inside ESI's cache window makes no HTTP call. Older entries are revalidated with ETags, and a 5xx serves the stale copy. A write invalidates the reads it affects. Keys are hashed per token.
Rate limitingOne bucket per ESI rate-limit group: 46 of them, generated from the spec. The limiter learns from ESI's headers and honours Retry-After. A 420 or 429 blocks only its own group.
ResilienceExponential backoff with jitter, a single coalesced token refresh on 401, deduplication of identical in-flight GETs, and an opt-in circuit breaker. Each one is an interface you can replace.
PaginationOffset and cursor paging. stream* yields one validated page at a time, and fetchAll* fetches pages concurrently. batch and batchPost handle fan-out.
AuthenticationEVE SSO with PKCE, and a token manager that handles storage, proactive refresh, rotation, revocation and bulk refresh for many characters.
Static data./sde answers offline queries over CCP's Static Data Export: 99 typed lookups with no database. It shares no code with the HTTP pipeline, and a lint rule enforces that. It is tested to the same bar as the client (scorecard).
Correct wire formatWhere the specification is wrong about how ESI reads a request (parameters in the query rather than the body, undocumented length limits), the definitions follow ESI. The specification documents this and live validation proved it.

The engineering stance ​

The project is run to a written engineering charter. It has 70 numbered requirements, each in the same EARS form as the test specification, and each with a status that says whether a machine enforces it: 55 are Enforced, 6 Practised, 8 Partial and 1 Gap. A gap is recorded, never hidden. Seven positions explain most of the choices:

  1. The custom clients are the product. ESI.ts is a library of hand-written, curated domain clients. Method names, argument shapes, wire fixes and validation strictness are product decisions. Generation serves the clients and never replaces them, and a gate fails any change that deprecates one (Client architecture).
  2. The OpenAPI spec is upstream. Types, cache TTLs, rate-limit groups, scopes and 235 typed operations are generated from it, and CI fails when they go stale. Drift reports keep the hand-written code honest against it.
  3. Tolerate additive change. New fields and enum members from CCP never break a consumer. A removal is a breaking change.
  4. Resilience is pluggable. Retry, rate limiting, circuit breaking, deduplication, caching and transport are interfaces, not imports.
  5. Secure by construction. HTTPS and a host allowlist are checked at construction, tokens are attached only where a scope is declared, URLs are redacted in every error, and cache keys are hashed per token. Each control is a test.
  6. The specification executes. Behaviour is written as EARS requirements in Gherkin, one per Rule:, and verified by scenarios that mock only the transport. The audit fails a pull request that weakens the wording.
  7. Verifiable supply chain. Every action is SHA-pinned and every token least-privilege. Releases carry npm provenance, a signed SBOM, cosign signatures and checksums.

By the numbers ​

Measured on master at 9486fe2c on 2026-09-28 with the commands shown; the mutation and Scorecard rows cite the run that produced them. TESTING.md has the full breakdown.

MeasureValueReproduce
Tests run offline10,045 tests in 319 suites; 9,837 run, 0 failing (208 live tests skip without credentials or an SDE export)npm test, fuzz, faults, contract:replay, test:integration
Unit, BDD and composition291 suites, 8,308 tests in about two and a half minutesnpm test
Coverage97.8% statements, 95.9% branches, 93.6% functions, 97.9% linesnpm run coverage (floors 90 / 80 / 75 / 90)
Executable specification664 EARS requirements, 863 scenarios, 77 feature filesnpm run ears
Property and fuzz tests1,240 tests; 10,000 runs a property nightlynpm run fuzz
Transport fault catalogue148 faults through the real pipelinenpm run faults
Recorded ESI payloads121 replay tests, re-recorded nightly with a drift PRnpm run contract:replay
Mutation score84.1% of 1,355 mutants in src/core killed by the unit suite (nightly of 2026-09-27); 100% in middleware, pagination and util; floors per directory, ratcheted nightly, changed files on every PRnpm run mutation:ratchet
Static data (SDE)156 EARS requirements and 223 scenarios in 23 feature files; at least 90% of mutants killed in every SDE directory by the unit suite and by the BDD scenarios alone (2026-10-08, #458); a real-data run against CCP's current export every nightsde/TESTING.md
CIOne required check (ci-success) over the full matrix; 29 workflows, 18 of them scheduledQUALITY-GATES.md
OpenSSF Scorecard8.3 / 10 on 2026-09-28; the checks still open need settings only the maintainer can changeSECURITY.md

Every tier has to prove it can fail: a negative fixture, a killed mutant or a caught fault. Every floor is a one-way ratchet.

Packages in the box ​

ImportWhat it holds
@lgriffin/esi.tsEsiClient, EsiClientBuilder, EsiApiFactory, domain clients, auth, errors, generated types and scopes
@lgriffin/esi.ts/schemasThe Zod response schemas
@lgriffin/esi.ts/errorsError classes and type guards, including the auth errors
@lgriffin/esi.ts/testingcreateMockTransport and TestDataFactory for your own tests
@lgriffin/esi.ts/clientcreateEsi, the spec-shaped low-level surface: esi.public (authenticated calls do not compile), esi.as(identity)
@lgriffin/esi.ts/sdeSdeDataProvider (YAML and ZIP, through the optional peers js-yaml and adm-zip) and MemorySdeProvider
@lgriffin/esi.ts/sde/memoryMemorySdeProvider alone, with no file-system or parser code, for browsers and bundles
typescript
import { MarketOrderSchema } from '@lgriffin/esi.ts/schemas';
import { EsiError, isRetryable } from '@lgriffin/esi.ts/errors';
import { SdeDataProvider } from '@lgriffin/esi.ts/sde';
import { createConsoleLogger } from '@lgriffin/esi.ts';

const log = createConsoleLogger('info');

const sdeData = SdeDataProvider.fromDirectory('./sde-data');
log.info(`Type 34: ${sdeData.getType(34)?.name}`); // Type 34: Tritanium
sdeData.close();

Guides ​

The README orients and the guides are canonical. Each guide opens with the charter requirements it implements.

Using ESI.ts
Using the clientConstruction, configuration, every domain client, metadata, batching, examples, 11.0 and 12.0 changes
AuthenticationTokens, refresh on 401, SSO with PKCE, the multi-character token manager
Many charactersOne runtime, esi.public, esi.as(identity), what the views share, moving from createClient
PaginationOffset and cursor paging, stream*, fetchAll*, failure behaviour
ErrorsError classes, type guards, retryability, safe mode
Runtime validationZod response and request validation
LoggingILogger, per-client loggers, pino, ESI_LOG_LEVEL
Static data (SDE)The offline Static Data Export module: its role, isolation, API (module README)
How it is built
Engineering charterThe requirements the project holds itself to, with status and gap register
RoadmapPhases, definitions of done, the SDE programme, the release gate
Lean decisionsWhy it is run this way: every decision since v7, with value stream maps
ArchitectureLayers, ports, the request path, caching, retry, rate limiting, circuit breaker
Client architectureCustom clients are the product; the generated layer underneath; the plan
Design rulesNaming, schemas, adding an endpoint or a client, generated files
TestingEvery test tier, what it proves, how to run it
Mutation testingStryker shards, floors and the ratchet
Quality gatesWhat runs at commit, push, PR, nightly and release; every workflow
SecurityRuntime defences and supply-chain controls (policy)
Semantic versioningWhat is public; major, minor or patch; breaking-change commits
ReleaseCutting a release, changelog, provenance, signatures, support window
AuditThe Phase 0 measured baseline for 11.0
OKF bundleThe generated Open Knowledge Format catalogue of ESI
DocumentationDocumentation surfaces, checked examples, TypeDoc
BeadsIssue tracking workflow

Examples ​

examples/ has 58 runnable scripts, each with an npm script. The public ones run against live ESI every night, and a failure opens an issue.

bash
npm run example:status      # quickest smoke test, no token
npm run example:market      # prices and history
npm run example:streaming   # stream* over a large region
npm run example             # full character profile (ESI_ACCESS_TOKEN)

The full list is in USAGE.md, and the examples showcase has a page for each with its source, what it needs and the command that runs it.

Contributing ​

bash
git clone https://github.com/lgriffin/ESI.ts.git && cd ESI.ts
npm ci
npm run check:local -- --fast   # every offline CI tier, minus the build

Read CONTRIBUTING.md first. Every behaviour change starts with an EARS requirement and a failing scenario. Every commit is a conventional commit classified by SEMVER.md. No floor is ever lowered. Work is tracked in GitHub issues labelled by release and phase, mirrored in beads.

License ​

GPL-3.0-or-later. See LICENSE for the licence text and NOTICE for the copyright line and CCP's trademark notice; EVE Online is the property of CCP hf. and this project is not affiliated with or endorsed by them.


o7

Released under the GPL-3.0-or-later license.