Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Building a Lobby Browser for Age of Empires II: Definitive Edition

This guide covers how to interact with the Worlds Edge Link API to build custom lobby browsers and spectator tools for Age of Empires games.

Overview

The Worlds Edge Link API provides two main approaches for accessing lobby data:

  • Community API - Public, unauthenticated access to lobby listings
  • Game API - Authenticated access to ongoing matches and detailed game data

For simpler integration, you can also use the aoe2lobby.com WebSocket API which provides real-time updates without polling.

Quick Start: Community API

Basic Lobby Listing

For a simple lobby browser, use the community endpoint /advertisement/findAdvertisements:

GET https://aoe-api.worldsedgelink.com/community/advertisement/findAdvertisements?title=age2

Features:

  • No authentication required
  • Public lobby data only
  • Recommended polling interval: 5 seconds with client-side caching
  • No WebSocket/push notification support

This is sufficient for most lobby browsing use cases.

Advanced: Game API (Authenticated)

For accessing ongoing matches and spectator features, you’ll need to authenticate using Steam.

We provide a reference implementation written in Python that you may want to have a look at.

Prerequisites

  • Steam account with the game purchased
  • Node.js environment (examples use JavaScript)
  • ’‘steam-user’’ npm package for Steam authentication

Authentication Flow

Step 1: Create Encrypted App Ticket

const SteamUser = require('steam-user');
const client = new SteamUser();

// Login to Steam first (with your credentials)
await client.logOn({...});

// Create encrypted app ticket
const APP_ID = 813780; // AoE2:DE
const eticket = await client.createEncryptedAppTicket(
    APP_ID, 
    Buffer.from("RLINK")  // Important: Include RLINK buffer
);

// Encode for API use
const auth = encodeURIComponent(
    eticket.encryptedAppTicket.toString('base64')
);

Important Notes:

  • Always include ’‘Buffer.from(“RLINK”)’’ as the second parameter
  • Don’t spam login attempts - Steam will rate limit and require verification codes
  • The auth string must be both Base64 and URI encoded

Step 2: Platform Login

const url = `https://aoe-api.worldsedgelink.com/game/login/platformlogin?`
    + `accountType=STEAM`
    + `&activeMatchId=-1`
    + `&alias=${alias}`
    + `&appID=${APP_ID}`
    + `&auth=${auth}`  // Your encoded ticket from Step 1
    + `&callNum=0`
    + `&clientLibVersion=190`  // Update as game updates
    + `&country=US`
    + `&installationType=windows`
    + `&language=en`
    + `&macAddress=DE-AD-D0-0D-00-00`  // Can be any value
    + `&majorVersion=4.0.0`
    + `&minorVersion=0`
    + `&platformUserID=${steamID}`
    + `&timeoutOverride=0`
    + `&title=age2`;

const response = await axios.post(url);
const sessionId = response.data.sessionID; // Save this!

Key Parameters:

  • ’‘clientLibVersion’’ - Currently 190+ (changes with game updates)
  • ’‘macAddress’’ - Can be any valid format
  • ’‘auth’’ - Your encoded ticket from Step 1
  • Returns a ’‘sessionID’’ needed for subsequent requests

Getting Game Build Version

The ’‘appBinaryChecksum’’ parameter requires the current game build version. You can obtain this programmatically:

const getBuildVersion = async (appId) => {
    const rss = await fetch(
        `https://store.steampowered.com/feeds/news/app/${appId}/`
    );
    const parser = new XMLParser();
    const feed = parser.parse(await rss.text());
    
    let lastUpdateTitle = feed?.rss?.channel?.item
        ?.map(e => e.title)
        .find(e => e.match(/(update.[0-9]+)/gi));
        
    return parseInt(
        lastUpdateTitle.slice(
            lastUpdateTitle.search(/update/gi) + "update".length
        )
    );
}

Alternatively, check the bottom-left corner when launching the game.

Finding Observable Matches

Once authenticated, query ongoing games:

const url = `https://aoe-api.worldsedgelink.com/game/advertisement/findObservableAdvertisements?`
    + `appBinaryChecksum=${gameVersion}`
    + `&callNum=0`
    + `&count=50`
    + `&dataChecksum=0`
    + `&desc=1`
    + `&matchType_id=0`
    + `&modName=INVALID`
    + `&modDLLChecksum=0`
    + `&modDLLFile=INVALID`
    + `&modVersion=INVALID`
    + `&start=0`
    + `&sortOrder=1`
    + `&versionFlags=56950784`
    + `&sessionID=${sessionId}`
    + `&connect_id=${sessionId}`;

const response = await axios.get(url);

Filtering Options

By Player Profiles

&profile_ids=[123456,789012]

Filter matches to only show games with specific player profile IDs.

By Numeric Tags

&numericTagNames=["HasPassword"]
&numericTagValues=[0]

Common tags include:

  • ’‘HasPassword’’ - Filter by password-protected status
  • Other tags available but not fully documented

Response Format

Warning: The Game API returns responses in an obscured array format that is not user-friendly.

// Example response structure
[
    someMetadata,
    [  // Array of matches
        [lobbyId, platformSessionId, ...otherFields],
        [lobbyId, platformSessionId, ...otherFields],
        // etc.
    ]
]

Parsing Help:

  • Responses are positional arrays, not named objects
  • Element positions are stable across API versions
  • See this reference implementation for field mappings
  • The second element of the response contains the array of advertisements
  • Each advertisement follows the order: lobby ID, platform session ID, followed by other fields

Alternative: WebSocket API

For simpler integration, consider using the aoe2lobby.com WebSocket API:

  • Real-time updates (no polling needed)
  • Clean, documented data format
  • Provides schema information on connection
  • Free to use for community projects
  • Live for 8+ months with proven reliability

Connection

WebSocket URL: ``` wss://data.aoe2lobby.com/ws/

The API supports subscribing to different data feeds by sending JSON subscription messages.

### Subscription Examples
**Subscribe to spectate matches:**
```json

{"action":"subscribe","type":"matches","context":"spectate"}

Subscribe to lobby matches:

{ "action": "subscribe", "type": "matches", "context": "lobby" }

Subscribe to specific Elo types:

{
    "action": "subscribe",
    "type": "elotypes",
    "context": "lobby",
    "ids": ["1223", "3", "4"]
}

Subscribe to specific players:

{
    "action": "subscribe",
    "type": "players",
    "context": "lobby",
    "ids": ["3920944"]
}

Player status updates include current status, match ID, and Steam lobby ID:

{
    "player_status": {
        "19501096": {
            "status": "lobby",
            "matchid": "412015195",
            "steam_lobbyid": "109775244730862406"
        }
    }
}

Resources

Best Practices

Read the Age of Empires API Usage Guidelines!

Rate Limiting

  • Server implements rate limiting - don’t spam requests
  • Use reasonable polling intervals (5+ seconds)
  • Cache results when possible
  • Consider batch operations for multiple profile lookups

Version Management

  • ’‘clientLibVersion’’ changes with game updates
  • Can be found by inspecting network traffic with Wireshark
  • Hardcoded in the game binary/PE file
  • Current version as of late 2024: 190+

Authentication

  • Session IDs persist for some time
  • Avoid repeated Steam logins (triggers verification)
  • Use a dedicated Steam account for service authentication
  • Consider running auth service separately from main app

Additional Resources

Documentation & Projects

Community Support

  • AOE API developer channels
  • Libre:Match Discord
  • Various open source projects with working examples

Contributing

This documentation is community-maintained. If you discover new endpoints, parameters, or corrections, please contribute back to help other developers!


*Last updated: December 2025 *

*Based on community research and shared knowledge *