@lgriffin/esi.ts - EVE Online ESI API Client - v12.3.0
    Preparing search index...

    @lgriffin/esi.ts - EVE Online ESI API Client - v12.3.0

    ESI.ts logo

    ESI.ts

    npm version License: GPL v3 TypeScript CI/CD Pipeline Coverage Mutation score Tests EARS requirements npm downloads OpenSSF 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

    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.

    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:

    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.

    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.

    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.

    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:

    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.

    Capability What ESI.ts does
    Full coverage 39 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 validation Every 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.
    Caching A 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 limiting One 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.
    Resilience Exponential 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.
    Pagination Offset and cursor paging. stream* yields one validated page at a time, and fetchAll* fetches pages concurrently. batch and batchPost handle fan-out.
    Authentication EVE 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 format Where 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 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.

    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.

    Measure Value Reproduce
    Tests run offline 10,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 composition 291 suites, 8,308 tests in about two and a half minutes npm test
    Coverage 97.8% statements, 95.9% branches, 93.6% functions, 97.9% lines npm run coverage (floors 90 / 80 / 75 / 90)
    Executable specification 664 EARS requirements, 863 scenarios, 77 feature files npm run ears
    Property and fuzz tests 1,240 tests; 10,000 runs a property nightly npm run fuzz
    Transport fault catalogue 148 faults through the real pipeline npm run faults
    Recorded ESI payloads 121 replay tests, re-recorded nightly with a drift PR npm run contract:replay
    Mutation score 84.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 PR npm 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 night sde/TESTING.md
    CI One required check (ci-success) over the full matrix; 29 workflows, 18 of them scheduled QUALITY-GATES.md
    OpenSSF Scorecard 8.3 / 10 on 2026-09-28; the checks still open need settings only the maintainer can change SECURITY.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.

    Import What it holds
    @lgriffin/esi.ts EsiClient, EsiClientBuilder, EsiApiFactory, domain clients, auth, errors, generated types and scopes
    @lgriffin/esi.ts/schemas The Zod response schemas
    @lgriffin/esi.ts/errors Error classes and type guards, including the auth errors
    @lgriffin/esi.ts/testing createMockTransport and TestDataFactory for your own tests
    @lgriffin/esi.ts/client createEsi, the spec-shaped low-level surface: esi.public (authenticated calls do not compile), esi.as(identity)
    @lgriffin/esi.ts/sde SdeDataProvider (YAML and ZIP, through the optional peers js-yaml and adm-zip) and MemorySdeProvider
    @lgriffin/esi.ts/sde/memory MemorySdeProvider alone, with no file-system or parser code, for browsers and bundles
    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();

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

    Using ESI.ts
    Using the client Construction, configuration, every domain client, metadata, batching, examples, 11.0 and 12.0 changes
    Authentication Tokens, refresh on 401, SSO with PKCE, the multi-character token manager
    Many characters One runtime, esi.public, esi.as(identity), what the views share, moving from createClient
    Pagination Offset and cursor paging, stream*, fetchAll*, failure behaviour
    Errors Error classes, type guards, retryability, safe mode
    Runtime validation Zod response and request validation
    Logging ILogger, 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 charter The requirements the project holds itself to, with status and gap register
    Roadmap Phases, definitions of done, the SDE programme, the release gate
    Lean decisions Why it is run this way: every decision since v7, with value stream maps
    Architecture Layers, ports, the request path, caching, retry, rate limiting, circuit breaker
    Client architecture Custom clients are the product; the generated layer underneath; the plan
    Design rules Naming, schemas, adding an endpoint or a client, generated files
    Testing Every test tier, what it proves, how to run it
    Mutation testing Stryker shards, floors and the ratchet
    Quality gates What runs at commit, push, PR, nightly and release; every workflow
    Security Runtime defences and supply-chain controls (policy)
    Semantic versioning What is public; major, minor or patch; breaking-change commits
    Release Cutting a release, changelog, provenance, signatures, support window
    Audit The Phase 0 measured baseline for 11.0
    OKF bundle The generated Open Knowledge Format catalogue of ESI
    Documentation Documentation surfaces, checked examples, TypeDoc
    Beads Issue tracking workflow

    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.

    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.

    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.

    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