Skip to content
eve-esi-api logo

EVE Online ESI API Guidelines

eve-esi-api

EVE Online ESI API patterns and zkillboard integration. Covers ESI endpoints, authentication, data models, zkillboard RedisQ listener, caching strategies, and EVE-specific IDs (characters, corporations, alliances, ships, systems, regions). Use when working with killmail data, fetching EVE univers...

SKILL.md

Full skill instructions

EVE Online ESI API Guidelines

Purpose

Patterns for interacting with the EVE Online ESI API and zkillboard data feeds in this project.

When to Use

  • Fetching data from ESI (characters, systems, ships, etc.)
  • Processing killmail data from zkillboard
  • Implementing ESI authentication (SSO)
  • Caching EVE universe data
  • Working with EVE-specific IDs and data structures

Key URLs and Endpoints

ServiceBase URLPurpose
ESIhttps://esi.evetech.net/​latest/Official EVE API
zkillboard RedisQhttps://zkillredisq.stream/​listen.phpReal-time killmail feed
zkillboardhttps://zkillboard.com/Killmail browser
Fuzzworkhttps://www.fuzzwork.co.uk/​api/Third-party celestial data
EVE SSOhttps://login.eveonline.com/OAuth authentication
EVE Imageshttps://images.evetech.net/Character/​ship/​alliance icons

ESI Client Pattern

Basic Structure

pub struct EsiClient {
    client: Client,  // reqwest::Client
}

impl EsiClient {
    pub fn new() -> Self {
        EsiClient {
            client: Client::new(),
        }
    }

    async fn fetch<T: for<'de> Deserialize<'de>>(
        &self,
        path: &str,
    ) -> Result<T, Box<dyn Error + Send + Sync>> {
        let url = format!("{}{}", ESI_URL, path);
        let response = self.client.get(&url).send().await?;
        if !response.status().is_success() {
            return Err(format!("ESI API returned status: {}", response.status()).into());
        }
        let data: T = response.json().await?;
        Ok(data)
    }
}

Common ESI Endpoints

// Get system info
pub async fn get_system(&self, system_id: u32) -> Result<System, Error> {
    self.fetch(&format!("universe/​systems/​{}/", system_id)).await
}

// Get ship/​item group
pub async fn get_ship_group_id(&self, type_id: u32) -> Result<u32, Error> {
    #[derive(Deserialize)]
    struct EsiType { group_id: u32 }
    let type_info: EsiType = self.fetch(&format!("universe/​types/​{}/", type_id)).await?;
    Ok(type_info.group_id)
}

// Get names (POST endpoint)
pub async fn get_name(&self, id: u64) -> Result<String, Error> {
    let names: Vec<EsiName> = self.client
        .post(format!("{}universe/​names/", ESI_URL))
        .json(&[id])  // Array of IDs
        .send()
        .await?
        .json()
        .await?;
    Ok(names.into_iter().next()?.name)
}

// Get character affiliation (POST endpoint)
pub async fn get_character_affiliation(&self, character_id: u64)
    -> Result<(u64, Option<u64>), Error>  // (corp_id, alliance_id)
{
    let affiliations: Vec<Affiliation> = self.client
        .post(format!("{}characters/​affiliation/", ESI_URL))
        .json(&[character_id])
        .send()
        .await?
        .json()
        .await?;
    let a = affiliations.into_iter().next()?;
    Ok((a.corporation_id, a.alliance_id))
}

zkillboard RedisQ

Listener Pattern

const REDISQ_URL: &str = "https://zkillredisq.stream/​listen.php";

pub struct RedisQListener {
    client: Client,
    url: String,
}

impl RedisQListener {
    pub fn new(queue_id: &str) -> Self {
        // Each listener needs a unique queue ID
        let url = format!("{}?queueID={}", REDISQ_URL, queue_id);
        RedisQListener {
            client: Client::new(),
            url,
        }
    }

    pub async fn listen(&self) -> Result<Option<ZkData>, Error> {
        let response = self.client.get(&self.url)
            .timeout(Duration::from_secs(60))  // Long-polling
            .send()
            .await?;

        let wrapper: RedisQResponse = response.json().await?;
        Ok(wrapper.package)  // None if no new killmails
    }
}

Main Loop Pattern

loop {
    match listener.listen().await {
        Ok(Some(zk_data)) => {
            info!("[Kill: {}] Received", zk_data.kill_id);
            process_killmail(&zk_data).await;
            sleep(Duration::from_secs(1)).await;
        }
        Ok(None) => {
            // No new data, RedisQ timed out
            sleep(Duration::from_secs(1)).await;
        }
        Err(e) => {
            error!("Error: {}", e);
            sleep(Duration::from_secs(5)).await;  // Backoff on error
        }
    }
}

Data Models

Killmail Structure

#[derive(Debug, Deserialize)]
pub struct ZkData {
    pub kill_id: u64,
    pub killmail: KillmailData,
    pub zkb: ZkbMeta,
}

#[derive(Debug, Deserialize)]
pub struct KillmailData {
    pub killmail_id: u64,
    pub killmail_time: String,  // RFC3339 format
    pub solar_system_id: u32,
    pub victim: Victim,
    pub attackers: Vec<Attacker>,
}

#[derive(Debug, Deserialize)]
pub struct Victim {
    pub ship_type_id: u32,
    pub character_id: Option<u64>,
    pub corporation_id: Option<u64>,
    pub alliance_id: Option<u64>,
    pub position: Option<Position>,
}

#[derive(Debug, Deserialize)]
pub struct Attacker {
    pub ship_type_id: Option<u32>,
    pub weapon_type_id: Option<u32>,
    pub character_id: Option<u64>,
    pub corporation_id: Option<u64>,
    pub alliance_id: Option<u64>,
    pub final_blow: bool,
}

#[derive(Debug, Deserialize)]
pub struct ZkbMeta {
    pub total_value: f64,
    pub location_id: Option<u64>,
    pub esi: String,  // URL to full ESI killmail
}

System Data

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct System {
    pub id: u32,
    pub name: String,
    pub security_status: f64,
    pub region_id: u32,
    pub region: String,
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

Caching Strategy

Cache ESI Data Locally

// Check cache first
{
    let systems = app_state.systems.read().unwrap();
    if let Some(system) = systems.get(&system_id) {
        return Some(system.clone());
    }
}

// Fetch and cache
match esi_client.get_system(system_id).await {
    Ok(system) => {
        let mut systems = app_state.systems.write().unwrap();
        systems.insert(system_id, system.clone());
        save_systems(&systems);  // Persist to disk
        Some(system)
    }
    Err(e) => {
        warn!("Failed to fetch system {}: {}", system_id, e);
        None
    }
}

Using Moka for In-Memory Cache

use moka::future::Cache;

pub struct AppState {
    // Time-based expiry cache
    pub celestial_cache: Cache<u32, Arc<Celestial>>,
}

// Check cache
if let Some(celestial) = app_state.celestial_cache.get(&system_id) {
    return Some(celestial);
}

// Fetch and cache
let celestial = Arc::new(fetch_celestial().await?);
app_state.celestial_cache.insert(system_id, celestial.clone()).await;

EVE-Specific Knowledge

Important IDs

TypeExample IDsNotes
Ship Groups485 (Dread), 659 (Super), 30 (Titan)Used for filtering
Regions10000030 (Devoid), 10000012 (Curse)Universe IDs
Systems30000142 (Jita), 30002086 (Turnur)Solar system IDs

Ship Group Priorities

const SHIP_GROUP_PRIORITY: &[u32] = &[
    30,   // Titan
    659,  // Supercarrier
    4594, // Lancer
    485,  // Dreadnought
    1538, // FAX
    547,  // Carrier
    883,  // Capital Industrial Ship
    902,  // Jump Freighter
    513,  // Freighter
];

Image URLs

// Alliance logo
fn alliance_icon(id: u64) -> String {
    format!("https://images.evetech.net/​alliances/​{}/​logo?size=64", id)
}

// Corporation logo
fn corp_icon(id: u64) -> String {
    format!("https://images.evetech.net/​corporations/​{}/​logo?size=64", id)
}

// Ship/​item icon
fn ship_icon(id: u32) -> String {
    format!("https://images.evetech.net/​types/​{}/​icon?size=64", id)
}

// Character portrait
fn character_portrait(id: u64) -> String {
    format!("https://images.evetech.net/​characters/​{}/​portrait?size=64", id)
}

External Links

// zkillboard
fn zkb_kill(id: u64) -> String {
    format!("https://zkillboard.com/​kill/​{}/", id)
}
fn zkb_character(id: u64) -> String {
    format!("https://zkillboard.com/​character/​{}/", id)
}
fn zkb_corp(id: u64) -> String {
    format!("https://zkillboard.com/​corporation/​{}/", id)
}

// Dotlan
fn dotlan_system(id: u32) -> String {
    format!("http://evemaps.dotlan.net/​system/​{}", id)
}
fn dotlan_region(id: u32) -> String {
    format!("http://evemaps.dotlan.net/​region/​{}", id)
}

// EVE Tools battle report
fn br_link(system_id: u32, timestamp: &str) -> String {
    format!("https://br.evetools.org/​related/​{}/​{}", system_id, timestamp)
}

SSO Authentication

OAuth2 Flow

const ESI_AUTH_URL: &str = "https://login.eveonline.com/​v2/​oauth/​token";
const ESI_VERIFY_URL: &str = "https://login.eveonline.com/​oauth/​verify";

pub async fn exchange_code_for_token(
    &self,
    code: &str,
    client_id: &str,
    client_secret: &str,
) -> Result<EveAuthToken, Error> {
    // 1. Exchange authorization code for tokens
    let params = [("grant_type", "authorization_code"), ("code", code)];
    let auth_response: Value = self.client
        .post(ESI_AUTH_URL)
        .basic_auth(client_id, Some(client_secret))
        .form(&params)
        .send()
        .await?
        .json()
        .await?;

    let access_token = auth_response["access_token"].as_str()?.to_string();
    let refresh_token = auth_response["refresh_token"].as_str()?.to_string();

    // 2. Verify token and get character info
    let verify_response: Value = self.client
        .get(ESI_VERIFY_URL)
        .bearer_auth(&access_token)
        .send()
        .await?
        .json()
        .await?;

    let character_id = verify_response["CharacterID"].as_u64()?;
    let character_name = verify_response["CharacterName"].as_str()?.to_string();

    Ok(EveAuthToken { character_id, character_name, access_token, refresh_token, ... })
}

Authenticated Requests

pub async fn get_contacts(
    &self,
    entity_id: u64,
    token: &str,
    endpoint: &str,  // "characters", "corporations", or "alliances"
) -> Result<Vec<StandingContact>, Error> {
    let url = format!("{}{}/​{}/​contacts/", ESI_URL, endpoint, entity_id);
    let contacts: Vec<StandingContact> = self.client
        .get(&url)
        .bearer_auth(token)  // Include access token
        .send()
        .await?
        .json()
        .await?;
    Ok(contacts)
}

Reference Files

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View