ED Rare Router API
Version: Alpha 1.06
Last Updated: March 1, 2026
Author: R.W. Harper
LinkedIn: https://linkedin.com/in/rwhwrites
License: GNU General Public License v3.0
THIS IS A DEVELOPMENT/HOBBY PROJECT - USE AT YOUR OWN RISK
This API is provided "AS IS" without warranty of any kind, express or implied. No guarantees or warranties are given regarding accuracy, reliability, availability, or fitness for any purpose. The authors and contributors are not liable for any damages arising from use of this API. See the LICENSE file for full terms.
The ED Rare Router API provides endpoints for system autocomplete and rare goods scanning. All endpoints return JSON and use standard HTTP status codes.
Note: This is a quick scan tool for finding rare goods near your current location. Route planning is done manually by the user based on scan results.
Finance Ethos is automatically determined from the power parameter. Powers with Finance Ethos:
- Denton Patreus (Empire)
- Jerome Archer (Federation)
- Li Yong-Rui (Independent)
- Zemina Torval (Empire)
When a power with Finance Ethos is selected, the hasFinanceEthos flag is automatically set to true. Note: PowerPlay calculations are currently disabled (system type is always "none").
All endpoints are relative to the application root:
- Local:
http://localhost:4321(default port) - Custom port: Configure in
astro.config.mjsif using a different port
Endpoint: POST /api/setup
Description: Creates or overwrites .config.json in the project root. Used by the first-run setup page and when the user explicitly replaces existing config.
Request Headers: Content-Type: application/json
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
edsmUserAgent |
string | Yes (recommended) | User-Agent/contact for EDSM API (e.g. ED-Rare-Router/1.0 (contact: your@email.com)) |
dataDir |
string | null | No | Absolute path to data directory; null or empty = default data/ |
apiKeys |
object | No | Keys: edsm, eddn, etc. (lowercase). Values: API key strings. |
overwrite |
boolean | No | If true, replace existing .config.json. Required when config already exists. |
Response (201 Created):
{ "ok": true, "message": "Config saved." }Error Responses:
- 400 — Missing/invalid
Content-Typeor invalid JSON body. - 409 Conflict — Config already exists and
overwriteis nottrue.{ "error": "Config already exists. Edit .config.json manually or pass overwrite: true to replace." } - 500 — Failed to write file (permissions or path).
Notes: See Setup Guide for full first-run instructions.
Endpoint: GET /api/systems
Description: Provides system name suggestions for autocomplete functionality using the EDSM API.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
q |
string | Yes | Partial system name (minimum 2 characters) |
Example Request:
GET /api/systems?q=LaveResponse (200 OK):
[
{
"name": "Lave",
"coords": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
},
{
"name": "Lave Station",
"coords": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
}
]Response Headers:
Content-Type: application/jsonCache-Control: public, max-age=300(5 minutes)
Error Responses:
- 500 Internal Server Error: EDSM API failure
{ "error": "Failed to fetch systems" }
Notes:
- Results are cached for 15 minutes in memory
- Returns empty array
[]if query is less than 2 characters - Results are sorted by relevance (exact matches first)
Endpoint: GET /api/system-lookup
Description: Verifies if a system name exists in EDSM and returns system information. Useful for validating manually entered system names.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | System name to verify |
Example Request:
GET /api/system-lookup?name=SolResponse (200 OK - Found):
{
"found": true,
"system": {
"name": "Sol",
"coords": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"allegiance": "Federation",
"government": "Democracy"
}
}Response (200 OK - Not Found):
{
"found": false,
"system": null,
"message": "System \"InvalidSystem\" not found in EDSM database"
}Error Responses:
-
400 Bad Request: Missing system name
{ "error": "System name is required", "found": false, "system": null } -
500 Internal Server Error: Lookup error
{ "error": "Failed to lookup system" }
Response Fields:
| Field | Type | Description |
|---|---|---|
found |
boolean | Whether system was found in EDSM |
system |
object | null | System object if found, null otherwise |
message |
string | Human-readable message (only if not found) |
System Object (when found):
| Field | Type | Description |
|---|---|---|
name |
string | Exact system name (case-corrected) |
coords |
object | 3D coordinates (x, y, z) |
allegiance |
string | undefined | System allegiance (if available) |
government |
string | undefined | System government type (if available) |
Notes:
- Case-insensitive lookup
- Results are cached (in-memory + disk)
- Used by SystemInput component for validation
- Returns exact system name for case correction
Endpoint: POST /api/rares-scan
Description: Analyzes all rare goods from the current system perspective. Computes distances and evaluates legality.
Request Body:
interface ScanRequest {
current: string; // Current system name (required)
currentPpType: PpSystemType; // Always "none" (PowerPlay calculations disabled)
power: string; // Pledged power name (optional, for Finance Ethos detection)
hasFinanceEthos: boolean; // Automatically determined from power selection
}Example Request:
POST /api/rares-scan
Content-Type: application/json
{
"current": "Lave",
"currentPpType": "none",
"power": "Jerome Archer",
"hasFinanceEthos": true
}Response (200 OK):
[
{
"rare": "Lavian Brandy",
"originSystem": "Lave",
"originStation": "Lave Station",
"pad": "L",
"sellHintLy": 160,
"distanceToStarLs": 288,
"cost": 3500,
"permitRequired": false,
"distanceFromCurrentLy": 0.0,
"systemNotFound": false,
"legal": true,
"legalReason": "Legal",
"ppEligible": false,
"cpDivisors": null
},
{
"rare": "Altairian Skin",
"originSystem": "Altair",
"originStation": "Solo Orbiter",
"pad": "M",
"sellHintLy": 160,
"distanceToStarLs": 667,
"cost": 1325,
"permitRequired": false,
"distanceFromCurrentLy": 16.7,
"systemNotFound": false,
"legal": true,
"legalReason": "Legal",
"ppEligible": false,
"cpDivisors": null
}
]Response Fields:
| Field | Type | Description |
|---|---|---|
rare |
string | Name of the rare good |
originSystem |
string | System where rare originates |
originStation |
string | Station where rare can be purchased |
pad |
string | undefined | Landing pad size required: "S", "M", or "L" |
sellHintLy |
number | undefined | Optimal selling distance in lightyears |
distanceToStarLs |
number | undefined | Distance from arrival star to station in light seconds |
cost |
number | undefined | Typical market cost in credits |
permitRequired |
boolean | undefined | Whether the system requires a permit |
distanceFromCurrentLy |
number | Distance from current system to origin (lightyears) |
systemNotFound |
boolean | undefined | True if origin system coordinates couldn't be found in EDSM |
legal |
boolean | Whether rare is legal at current system |
legalReason |
string | Human-readable legality explanation |
legalityDetails |
object | undefined | Detailed legality information (see LegalityDetails below) |
ppEligible |
boolean | Always false (PowerPlay calculations disabled) |
cpDivisors |
object | null | Always null (PowerPlay calculations disabled) |
LegalityDetails Object:
| Field | Type | Description |
|---|---|---|
superpowerRestrictions |
string[] | Superpowers where illegal (all government types) |
illegalGovernments |
string[] | Government types where illegal (all superpowers) |
combinedRestrictions |
Array<{superpower: string, government: string}> | Specific superpower + government combinations where illegal |
legalGovernments |
string[] | Government types where legal (all except those in illegalGovernments) |
explanation |
string | Human-readable explanation of all restrictions |
CpDivisors Object:
| Field | Type | Description |
|---|---|---|
divisor |
number | Base CP divisor (5333) |
divisorWithFinanceEthos |
number | CP divisor with finance ethos (3555) |
effective |
number | Effective divisor based on hasFinanceEthos flag |
Error Responses:
-
400 Bad Request: Missing or invalid current system
{ "error": "Current system is required" } -
400 Bad Request: System not found in EDSM
{ "error": "Could not find coordinates for current system" } -
500 Internal Server Error: Processing error
{ "error": "Failed to scan rares" }
Notes:
- Processes all rare goods in the dataset
- Rare origin systems use cached data (from
data/rareSystemsCache.json) for faster responses - User-entered current system uses live EDSM API lookup
- Results are sorted by distance (closest first)
- PowerPlay calculations are disabled (
currentPpTypeis always "none") - Finance Ethos is automatically determined from the
powerparameter - Optional fields (pad, cost, etc.) may be
undefinedif not available in dataset systemNotFoundflag helps distinguish between "at origin" (distance 0) vs "system not found" (also distance 0)
type PpSystemType = "acquisition" | "exploit" | "reinforcement" | "none";interface ScanResult {
rare: string;
originSystem: string;
originStation: string;
pad?: string; // Landing pad size: "S", "M", or "L"
sellHintLy?: number; // Optimal selling distance (lightyears)
distanceToStarLs?: number; // Distance from star to station (light seconds)
cost?: number; // Typical market cost (credits)
permitRequired?: boolean; // Whether system requires permit
distanceFromCurrentLy: number; // Distance from current to origin (lightyears)
systemNotFound?: boolean; // True if origin system not found in EDSM
legal: boolean;
legalReason: string;
ppEligible: boolean; // Always false (PowerPlay disabled)
cpDivisors: CpDivisors | null; // Always null (PowerPlay disabled)
}interface CpDivisors {
divisor: number; // Base: 5333
divisorWithFinanceEthos: number; // With finance: 3555
effective: number; // Actual divisor to use
}Currently, there are no explicit rate limits. However:
- System autocomplete results are cached for 5 minutes (HTTP cache)
- System lookups are cached permanently (disk cache)
- EDSM API calls are minimized through aggressive caching
All endpoints follow these error handling principles:
- 400 Bad Request: Invalid or missing required parameters
- 500 Internal Server Error: Server-side processing errors
- Errors include a JSON object with an
errorfield containing a human-readable message
- Rare Origin Systems: Pre-generated cache file (
data/rareSystemsCache.json)- Loaded on application startup
- Used for all rare origin system lookups
- Provided as pre-built cache file (
data/rareSystemsCache.json)
- User-Entered Systems: In-memory cache (permanent) + disk cache (persistent)
- Current system uses live EDSM API lookups
- Cached after first lookup for performance
- System Autocomplete: HTTP cache (5 minutes) + in-memory cache (15 minutes)
- Cache-Control headers are set on the autocomplete endpoint
Endpoint: GET /api/curated-legality
Endpoint: POST /api/curated-legality
Endpoint: DELETE /api/curated-legality
Description: Manages manually curated legality data that overrides base data. Only available in development mode (import.meta.env.DEV === true). Returns 403 Forbidden in production.
GET Request: Returns all curated legality data
Response (200 OK):
{
"Kamitra Cigars": {
"illegalInSuperpowers": [],
"illegalInGovs": ["Prison Colony", "Theocracy", "Corporate"],
"illegalInSuperpowerGovs": [
{ "superpower": "Federation", "government": "Democracy" }
]
}
}POST Request: Updates curated data for a rare good
Request Body:
{
"rareName": "Kamitra Cigars",
"data": {
"illegalInSuperpowers": [],
"illegalInGovs": ["Prison Colony", "Theocracy", "Corporate"],
"illegalInSuperpowerGovs": [
{ "superpower": "Federation", "government": "Democracy" }
]
}
}DELETE Request: Removes curated data for a rare good (reverts to base data)
Request Body:
{
"rareName": "Kamitra Cigars"
}Security Note: These endpoints are restricted to development mode only. In production, all requests return 403 Forbidden.
Endpoint: GET /api/market-data
Description: Returns market data for rare goods stations. Reads from cached EDDN or EDSM data, falling back to live EDSM API if cache is stale.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
system |
string | Conditional | System name (required if station provided) |
station |
string | Conditional | Station name (required if system provided) |
rare |
string | Optional | Rare good name (filters to specific commodity) |
Examples:
Get market data for specific station and rare good:
GET /api/market-data?system=Lave&station=Lave Station&rare=Lavian BrandyGet all market data for a station:
GET /api/market-data?system=Lave&station=Lave StationGet market data by rare good name:
GET /api/market-data?rare=Lavian BrandyGet all cached market data:
GET /api/market-dataResponse (200 OK):
{
"found": true,
"system": "Lave",
"station": "Lave Station",
"rare": "Lavian Brandy",
"data": {
"name": "Lavian Brandy",
"buyPrice": 0,
"sellPrice": 0,
"stock": 0,
"stockBracket": 0,
"demand": 0,
"demandBracket": 0
},
"source": "cache",
"cacheFresh": true,
"metadata": {
"fetchedAt": "2026-01-12T12:00:00.000Z",
"totalEntries": 10
}
}Response Fields:
found: Boolean indicating if data was foundsystem: System name (if specified)station: Station name (if specified)rare: Rare good name (if specified)data: Market commodity data or full station market datasource: Data source ("cache" or "live")cacheFresh: Boolean indicating if cache is fresh (<12 hours old)metadata: Cache metadata (last fetched time, entry count)
Note: Market data depends on player contributions via EDMC. Data may not always be available or up-to-date for all stations.
Endpoints: GET /api/curated-prices, POST /api/curated-prices, DELETE /api/curated-prices
Description: Manage baseline purchase prices for rare goods. These prices are used as fallback when EDDN market data is not available. Only available in development mode.
IMPORTANT: These endpoints are only available when running in development mode (npm run dev). They return 403 Forbidden in production builds.
Returns all curated price data.
Response (200 OK):
{
"Lavian Brandy": {
"cost": 5000
},
"Centauri Mega Gin": {
"cost": 4500
}
}Updates or adds curated price data for a rare good.
Request Body:
{
"rareName": "Lavian Brandy",
"data": {
"cost": 5000
}
}Response (200 OK):
{
"success": true,
"rareName": "Lavian Brandy",
"data": {
"cost": 5000
}
}Validation:
rareNameis requiredcostmust be a non-negative number (optional - omit to remove curated entry)
Removes curated price data for a rare good.
Request Body:
{
"rareName": "Lavian Brandy"
}Response (200 OK):
{
"success": true,
"rareName": "Lavian Brandy",
"message": "Deleted"
}Price Priority: When displaying costs, the system uses this priority:
- EDDN live market data (if available) - shows "(Live)"
- Curated baseline price (if set) - shows "(Est.)"
- Static cost from rares.ts (if exists) - shows "(Est.)"
- "N/A" if none of the above
Data Storage: Curated prices are saved to data/curatedPrices.json.
- EDSM API: Used for system coordinates and information
- Base URL:
https://www.edsm.net/api-v1 - Endpoints:
/systems,/system - No authentication required
- Base URL: