Skip to content

Repository files navigation

CI NuGet

ESI.NET

A .NET wrapper for the EVE Online ESI API. Every ESI endpoint is a typed method; SSO, transparent token refresh, conditional requests, and the ESI error limit are handled for you.

Upgrading from 2023.12.12 or earlier? Every consumer needs (mechanical) code changes — see MIGRATION.md.

Install

dotnet add package ESI.NET

Setup

ESI.NET registers as a typed HttpClient through IHttpClientFactory.

Add an EsiConfig section to appsettings.json:

"EsiConfig": {
  "EsiUrl": "https://esi.evetech.net/",
  "DataSource": "Tranquility",
  "UserAgent": "my-app / my-character-name",
  "ClientId": "",
  "SecretKey": "",
  "CallbackUrl": ""
}
  • UserAgent is required. Use something that identifies you — a character and/or project name. CCP will contact you before cutting off access if they can tell who you are. The client throws on construction without it.
  • DataSource is Tranquility or Singularity (sent as ESI's X-Tenant).
  • ClientId / SecretKey / CallbackUrl are only needed for authenticated requests.

Every request pins an ESI compatibility date (X-Compatibility-Date), so the response shapes this package binds to stay fixed until you upgrade it. The current target is in ESI.NET.EsiVersion.CompatibilityDate.

Register it and take IEsiClient in your constructor:

// Program.cs
services.AddEsi(builder.Configuration.GetSection("EsiConfig"));

// or configure inline:
services.AddEsi(esi =>
{
    esi.EsiUrl = "https://esi.evetech.net/";
    esi.DataSource = DataSource.Tranquility;
    esi.UserAgent = "my-app / my-character-name";
});
public class MarketService
{
    private readonly IEsiClient _esi;
    public MarketService(IEsiClient esi) => _esi = esi;
}
Without dependency injection (.NET Framework, console apps)
using Microsoft.Extensions.Options;

var config = Options.Create(new EsiConfig
{
    EsiUrl = "https://esi.evetech.net/",
    DataSource = DataSource.Tranquility,
    UserAgent = "my-app / my-character-name",
});

var client = new EsiClient(config);

Making a request

EsiResponse<List<ResolvedInfo>> response = await _esi.Universe.Names(new List<int>
{
    1590304510, 99006319, 20000006
});

foreach (var item in response.Data)
    Console.WriteLine($"{item.Category}: {item.Name}");

Every call returns EsiResponse<T>:

member
Data the deserialized payload (T)
StatusCode the HTTP status
Message ESI's error string on failure, or a status message
Exception set if deserialization failed (Data is then null)
Pages total pages on a paginated endpoint (X-Pages)
ETag for the next conditional request
Expires / LastModified cache headers

Per-call options

Every method takes an optional trailing EsiCallOptions:

// a specific page, with cancellation
var groups = await _esi.Universe.Groups(new() { Page = 2, CancellationToken = ct });

// conditional request — 304 and no body if nothing changed
var prices = await _esi.Market.Prices(new() { IfNoneMatch = cached.ETag });
if (prices.StatusCode == HttpStatusCode.NotModified) { /* use your cache */ }

POST bodies

Simple POST endpoints take the values as method arguments. Where a method asks for object, build an anonymous object shaped like the JSON ESI expects (the ESI reference has the schema) — Json.NET serializes it.

Authenticated requests (SSO)

  1. Register an application at developers.eveonline.com and put its Client ID, Secret Key, and Callback URL into EsiConfig.

  2. Send the user to EVE SSO with the scopes you need and a state value you check on the way back:

    var url = _esi.SSO.CreateAuthenticationUrl(
        new List<string> { "esi-wallet.read_character_wallet.v1" },
        state: "a-value-you-verify-later");
    // redirect the browser to `url`
  3. On the callback, exchange the code and validate it:

    SsoToken token = await _esi.SSO.GetToken(GrantType.AuthorizationCode, code);
    AuthorizedCharacterData authChar = await _esi.SSO.Verify(token);
    // Verify throws InvalidOperationException if the token is bad.
  4. Persist authChar — at least RefreshToken and CharacterOwnerHash. On every re-login, compare the fresh CharacterOwnerHash to your stored one; a mismatch means the character was transferred and the stored data must be discarded.

  5. Pass the character on authenticated calls:

    var wallet = await _esi.Wallet.CharacterWallet(new() { Character = authChar });

Desktop, console, and GUI apps

No web server receives the callback, so register a loopback callback (http://localhost:<port>/callback) and catch the redirect with a short-lived HttpListener. A shipped app can't keep a client secret — register a native / PKCE application, leave EsiConfig.SecretKey empty, and use SSO.GenerateChallengeCode() with the challengeCode / codeChallenge overloads of CreateAuthenticationUrl and GetToken. Everything from GetToken on is identical to the web flow, and transparent refresh handles the no-secret case.

tools/MintToken is a complete, runnable example of this flow.

Token refresh

An authenticated call whose access token is within a minute of expiry is refreshed automatically before the request goes out, and authChar is updated in place. EVE rotates the refresh token, so you must persist the new value.

Handle that once, for every call, with an IEsiTokenRefreshSink:

public class DbTokenSink : IEsiTokenRefreshSink
{
    private readonly MyDbContext _db;
    public DbTokenSink(MyDbContext db) => _db = db;

    public async Task OnRefreshedAsync(AuthorizedCharacterData c)
    {
        _db.Characters.Update(c);
        await _db.SaveChangesAsync();
    }
}

services.AddScoped<IEsiTokenRefreshSink, DbTokenSink>();
services.AddEsi(builder.Configuration.GetSection("EsiConfig"));

The handler resolves the sink in a fresh scope each time it fires, so a scoped DbContext is safe. For one-offs or non-DI code, set OnTokenRefreshed on the call's EsiCallOptions instead.

Resilience

Add retries, timeouts, and a circuit breaker — reference Microsoft.Extensions.Http.Resilience and chain off AddEsi:

services.AddEsi(builder.Configuration.GetSection("EsiConfig"))
        .AddStandardResilienceHandler();

Links

Development

Maintainer tooling lives in tools/ — a daily spec-drift check against the live ESI OpenAPI document — and tests/ — unit tests plus live integration tests.

About

A C# wrapper for the Eve Online ESI API.

Resources

Stars

49 stars

Watchers

6 watching

Forks

Releases

Used by

Contributors

Languages