Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eseye Laravel Package

eveseat/eseye

ESI Eye is a Laravel client for EVE Online's ESI API, built for SeAT. It offers a fluent, expressive interface that handles authentication, token refresh, caching, and error handling, making it easy to query characters, corporations, alliances, and more from your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (or confirm it's already there). If you're writing a SeAT plugin, eveseat/esi (published as eveseat/eseye) is almost certainly already pulled in as a transitive dependency — check with composer show eveseat/esi before adding it yourself. Standalone Laravel apps can composer require eveseat/esi, but the package's token/refresh story is written with SeAT's models in mind, so most users run it inside SeAT.

The four objects you actually touch:

Class Role
EveSeat\Eseye\Eseye The fluent client. Endpoint building + invoke().
EveSeat\Eseye\Containers\EsiResponse What you get back. getBody(), getPages(), isCached(), getHeaders().
EveSeat\Eseye\Containers\EsiAuthentication Token bag (client id/secret, refresh token, scopes). Auto-refreshes.
EveSeat\Eseye\Configuration Singleton for user agent, cache store, rate limit, compatibility date.

Start by reading Eseye::invoke() and EsiResponse — everything else is plumbing around those two.

First call — a public endpoint with no auth. Prove your user agent and configuration are sane before touching tokens:

use EveSeat\Eseye\Configuration;
use EveSeat\Eseye\Eseye;

Configuration::getInstance()
    ->setUserAgent('my-seat-plugin/1.0 ([email protected])');

$esi  = new Eseye();
$type = $esi->invoke('get', '/universe/types/{type_id}/', ['type_id' => 587]);

echo $type->getBody()->name; // "Rifter"

Note the shape: the URI uses brace placeholders and the third argument is the substitution map — not a query string. Eseye fills them and handles the rest.

Second call — a character endpoint using a SeAT token:

use Seat\Eveapi\Models\RefreshToken;

$token = RefreshToken::find($characterId);

$esi = new Eseye(new EsiAuthentication([
    'client_id'     => config('eve.client_id'),
    'secret'        => config('eve.client_secret'),
    'refresh_token' => $token->refresh_token,
    'token'         => $token->token,   // current access token, if you stored one
    'scopes'        => $token->scopes,
]));

$assets = $esi->invoke('get', '/characters/{character_id}/assets/', [
    'character_id' => $characterId,
]);

$rows = $assets->getBody(); // array of stdClass

That's the whole first use case: "given a SeAT character, fetch an authenticated list and persist it from a queued job." Everything below is variations on it.

Implementation Patterns

Wrap it per ESI domain, don't leak it. Controllers, Blade views and models should never see an Eseye instance. Create small service classes — CharacterAssets, CorporationWallet, MarketOrders — that take a character/corporation ID, resolve the token, invoke, and return arrays of plain DTOs or Eloquent models. This gives you one place to add caching, retries and logging, and one place to swap in a fake for tests.

Push every authenticated fetch into a job. ESI is rate-limited and paginated; a 12-page asset walk inside a request will time out and torch your error budget. A ShouldQueue job per character, with $tries = 3 and a backoff() that grows, is the default shape for every SeAT plugin that ages well.

Pagination. EsiResponse::getPages() returns the total page count from the X-Pages header; Eseye::page() sets the page for the next invocation:

$first = $esi->invoke('get', '/characters/{character_id}/assets/', $args);
$all   = $first->getBody();

for ($page = 2; $page <= $first->getPages(); $page++) {
    $all = array_merge(
        $all,
        $esi->page($page)->invoke('get', '/characters/{character_id}/assets/', $args)->getBody()
    );
}

Remember to reset the page (->page(1)) if you reuse the client instance — the page is stateful on the object.

POST bodies and bulk endpoints. For endpoints like /universe/names/ or /characters/affiliation/, set the JSON body on the client rather than encoding it into the URI:

$names = $esi->setBody([587, 588, 589])
    ->invoke('post', '/universe/names/');

Free conditional-request caching. Eseye caches responses keyed on the URI and sends If-None-Match for you. On a cache hit you get a 304, getBody() returns null, and isCached() is true. Treat that as "the cached payload is authoritative" — read from your own persistence layer, not from the response body.

Error budget awareness. EsiResponse::getHeaders() exposes X-ESI-Error-Limit-Remain and X-ESI-Error-Limit-Reset. Log them centrally (a middleware or your wrapper's finally block) so you can see a plugin degrading before ESI starts returning 420s at you.

Persist refreshed tokens. ESI rotates refresh tokens. After any authenticated call that could have refreshed, read the container back ($esi->getAuthentication()) and write the new refresh token to SeAT's RefreshToken row (encrypted, via the model's cast) before the job ends. If SeAT's helper already does this for your version, don't duplicate it — but verify, because a dropped rotation surfaces later as invalid_grant and a dead character.

Testing. Eseye talks to ESI through its own Guzzle stack, so Http::fake() won't intercept it. Instead, bind a stub implementation of your wrapper interface into the container, or override the Configuration cache with an in-memory store so your test suite doesn't hit the network on repeats.

Gotchas and Tips

A user agent is not optional. ESI rejects or throttles requests with missing/generic agents. Set it once in a service provider's boot(), never per call — and include contact info, as CCP's policy asks.

Compatibility date. ESI now expects X-Compatibility-Date. Without it you're pinned to legacy behaviour that CCP has said will be removed. Set it in SeAT's config/eve.php / the Configuration singleton, and re-check it when you upgrade — a stale date is a silent behaviour change on the next major ESI deploy.

304 means "no body", not "error". The single most common bug in new plugins is $response->getBody()->something after a cached hit. Always branch on isCached() first.

Integers may arrive as floats or strings. Eseye decodes JSON into stdClass; depending on the compatibility date, quantity might be 100 or 100.0. Cast explicitly at the persistence boundary, and never use (array) casting on nested ESI objects and expect stable keys.

Caching will bite you in local development. Delete the file cache, or swap in an array cache in your local environment, or you'll spend an afternoon debugging data that ESI stopped serving ten minutes ago. Same applies in tests — state leaking between test methods is always the cache.

Don't catch \Exception. Catch EveSeat\Eseye\Exceptions\RequestFailedException and pull the attached EsiResponse for the status code and ESI's error message; catch InvalidAuthenticationException separately and mark the token as broken rather than retrying forever. 420/429 need backoff, 404 usually means "asset deleted in game, prune your row", 403 usually means "scope not granted".

Scopes are fixed at authorisation time. Adding a new endpoint to your plugin doesn't grant the scope — the user has to re-authorise. Ship scope changes with a migration/notice, and check in_array($scope, $token->scopes) before invoking so you can skip gracefully instead of 403-ing per character per run.

Null X-Pages. Some endpoints omit the header for single-page results. Guard the loop with $response->getPages() ?? 1 or you'll iterate zero times and silently return an empty collection.

Corporation endpoints use the same paths as character ones — /corporations/{corporation_id}/… — but need a token whose character has the right roles. A 403 here is a permissions problem in-game, not a code problem; log it that way so future-you doesn't go hunting through Eseye.

Extension points worth knowing: the Configuration singleton (user agent, cache driver, rate limit, compatibility date) is the sanctioned place to change global behaviour; the client itself is fluent and stateful, so a fresh new Eseye(...) per job is cheaper than reasoning about leaked page/body/header state.

Licence. GPL-2.0 — fine for internal SeAT installs, and it's already a dependency there, but check before vendoring into anything distributed under a different licence.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager