REST API
All helpers return promises and throw on HTTP errors (including 404 when no context exists yet — catch accordingly). Base URL: http://127.0.0.1:12345. The full interactive reference is APIExpose's own Swagger UI at http://127.0.0.1:12345/swagger.
| Helper | Endpoint | Returns |
|---|---|---|
getHealth() |
GET /api/v1/health |
{ status, version } |
getState() |
GET /api/v1/context/state |
{ state, selectedSystem, selectedGame, runningGame } |
getCurrentGame() |
GET /api/v1/context/current-game |
current selected/running game with details (image, video, developer, genre…) and launch info |
getCurrentSystem() |
GET /api/v1/context/current-system |
selected system |
getHiscores(params) |
GET /api/v1/hiscores |
hiscore table (400 without a supported game) |
getRetroAchievementsStatus() |
GET /api/retroachievements/status |
account/link status |
getRetroAchievementsSession() |
GET /api/retroachievements/session |
live session (game, unlocks, hardcore…) |
getCurrentPanel() |
GET /api/v1/panels/current |
resolved control-panel layout (404 without a game) |
Arcade outputs are event-only
Arcade/MAME output signals (lamps…) change too fast for a REST snapshot to be meaningful. Subscribe to arcade.output.changed instead — see Events.
Context lifecycle
state is browsing while navigating EmulationStation and playing while a game runs. Right after APIExpose starts, the context can be empty (selectedGame: null, current-game → 404) until ES emits its next selection. Robust pattern:
async function currentGameOrNull(client) {
try {
const game = await client.getCurrentGame();
if (game && !game.message) return game;
} catch { /* 404: no context yet */ }
const state = await client.getState();
return state?.runningGame ?? state?.selectedGame ?? null;
}
Media URLs — mediaUrl(path)
APIExpose is the single media source. Its canonical media store consolidates and enriches everything (artwork, wheels, marquees, mixes, manuals, videos…), and details.* / details.extras.* expose ready-to-use /api/v1/media/... URLs:
{
"image": "/api/v1/media/systems/megadrive/games/sonic_the_hedgehog/artwork/screentitle.png",
"marquee": "/api/v1/media/systems/megadrive/games/sonic_the_hedgehog/ui/wheels/wheel.png",
"extras": { "cartridge": "/api/v1/media/systems/megadrive/games/sonic_the_hedgehog/artwork/cartridge.png" }
}
mediaUrl() turns any of them (or any media-store-relative path) into an absolute URL; absolute http(s):// inputs pass through unchanged:
const game = await client.getCurrentGame();
img.src = client.mediaUrl(game.details.marquee);
Commands
Launching a game (used to validate this SDK live):
await fetch('http://127.0.0.1:12345/api/v1/commands/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ romPath: 'E:/RetroBat/roms/megadrive/Sonic The Hedgehog (USA, Europe).zip' })
}); // → 202 { status: "launching" }
ES control endpoints
The EsController tap/combo endpoints work, but the goto-system / goto-game navigation endpoints are currently unreliable. Prefer commands/launch for automation.
CORS
Browser pages call this API cross-origin. APIExpose sends Access-Control-Allow-Origin: * starting with 1.1.2; on older versions REST calls from a browser are blocked (WebSocket is unaffected).