Skip to main content

Getting started with Idem Self-hosted

This page is guiding you through the process of setting up Idem Self-hosted for use with your game.

This is split into three steps:

① Get your personal license

② Host the container

③ Integrate the matchmaking API in your game

① Get your personal license key

To get started as quickyl as possible, get your personal license key for the community edition by heading to our website and registering with your name and email. We will immediately send you a license key via mail.

If you are interested in using the advanced matchmaker, reach out to us at match@idem.gg.

tip

You can always start with the community edition an upgrade to advanced later.

② Host the container

Idem Self hosted is distributed via Docker Hub. There are two options to download and run it:

Using docker run

You can start the container with the following command:

docker run -d \
-p 8000:8000 \
-e LICENSE_KEY="your_license_key_here" \
idemmatchmaking/idem-selfhosted:stable

Using docker-compose

If you prefer to use Docker Compose, create a docker-compose.yml file with the following content:

services:
matchmaking:
image: idemmatchmaking/idem-selfhosted:stable
ports:
- "8000:8000"
environment:
LICENSE_KEY: "your_license_key_here"

Then start the container with:

docker-compose up -d

In both cases, your Idem Self-hosted container should now be running and accessible on port 8000.

note

The container needs access to license.idem.gg for being able to validate the license. Otherwise it won't function properly.

③ Integrate the matchmaking API in your game

As described in the introduction, Idem Self-hosted is built as a stateless matchmaking system. This means you at any time have full control over how you manage your queue. You can build this yourself or integrate with another system like Open Match.

Each request to the matchmaking API includes all tickets in your queue as well as the configuration for the matchmaker. This is easiest understood by looking at examples. Let's start with a basic one.

Basic example

Request

The following payload shows the most basic request:

{
"game_mode": {
"numTeams": 2,
"numPlayersPerTeam": 1
},
"tickets": [
{
"ticketId": "player1",
"joinTimestamp": 1741441659,
"rating": 1500,
"servers": ["eu-central-1"]
},
{
"ticketId": "player2",
"joinTimestamp": 1741441660,
"rating": 1500,
"servers": ["eu-central-1"]
}
]
}

Let's look at the individual pieces.

The game_mode key includes the most basic information needed to tell the matchmaker what to do:

  • numTeams - This identifies how many teams each game should have
  • numPlayerPerTeam - This identifies how many players should be in each team

Taken together, this defines a 2v2 mode.

note

This is a minimal example. There is many more options to configure a game mode. See Customization for all the details.

The tickets key contains the information about your players' tickets. Each ticket has the following properties:

  • ticketId - A unique identifier for the ticket
  • joinTimestamp - The time the ticket was created, i.e. typically the player started to look for a match
  • rating - A numeric metric used to assess "soft" compatibility of tickets
  • servers - A list of servers that the player could play on
note

In most cases, rating is a representaiton of skill, like TrueSkill or ELO. But we have seen other values being used, like trophy count, games played, or even difficulty selected (for coop games). You can use anything as long as it is a numeric value.

note

If you are using the advanced matchmaker, servers can also be a latency map. When provided, Idem will identify the optimal server for a matchbased on the latency.

This payload needs to be send to the container's matchmaking endpoint. Depending on the version that you are using, this is:

  • community: POST /matchmaking/basic
  • advanced: POST /matchmaking/advanced

Response

The endpoint will respond with an answer like this:

{
"matchSuggestions": [
{
"id": "bed26b90-57ae-4d89-b7f5-ca08e8638fc3",
"server": "eu-central-1",
"matchScore": 100,
"teams": [
{
"id": "e1b22f9e-4f69-42cf-a793-2dcd1fc293f9",
"tickets": [
{
"ticketId": "player1",
"joinTimestamp": 1741441659,
"rating": 1500,
"servers": ["eu-central-1"],
"exactEqualityConstraints": null
}
]
},
{
"id": "a3c44f9e-7b59-12af-c493-8acb1fc123a8",
"tickets": [
{
"ticketId": "player2",
"joinTimestamp": 1741441660,
"rating": 1500,
"servers": ["eu-central-1"],
"exactEqualityConstraints": null
}
]
}
]
}
],
"unmatchedTickets": []
}

This includes the one match expected based on the input. Since there are no unmatchable players and no invalid tickets, those lists are empty.

note

If you try to replicate this example, you might not get back a match if the two tickets have joinTimestamps very close to the current time. This is expected since the algorithm weighs match quality against waiting time. In that case, both tickets will be returned in unmatchedTickets. You can learn more about this here.

Extended example

Let's now look at an extended example.

Request

{
"game_mode": {
"numTeams": 2,
"numPlayersPerTeam": 2
},
"tickets": [
{
"ticketId": "player3",
"joinTimestamp": 1741614693,
"rating": 1501,
"servers": ["eu-central-1", "us-west-1", "asia-east-1"]
},
{
"ticketId": "player7",
"joinTimestamp": 1741614694,
"rating": 1503,
"servers": ["eu-central-1", "us-east-1"]
},
{
"ticketId": "player2",
"joinTimestamp": 1741614695,
"rating": 1505,
"servers": ["eu-central-1", "us-west-1", "eu-west-1"]
},
{
"ticketId": "player8",
"joinTimestamp": 1741614696,
"rating": 1507,
"servers": ["eu-central-1", "asia-south-1", "eu-west-1"]
},
{
"ticketId": "player5",
"joinTimestamp": 1741614697,
"rating": 1511,
"servers": ["eu-west-1", "us-east-1", "asia-north-1"]
},
{
"ticketId": "player10",
"joinTimestamp": 1741614698,
"rating": 1513,
"servers": ["eu-west-1", "us-south-1", "us-east-1"]
},
{
"ticketId": "player1",
"joinTimestamp": 1741614699,
"rating": 1515,
"servers": ["eu-west-1", "us-east-1", "asia-south-1"]
},
{
"ticketId": "player9",
"joinTimestamp": 1741614700,
"rating": 1517,
"servers": ["eu-west-1", "us-east-1", "asia-north-1"]
},
{
"ticketId": "player4",
"joinTimestamp": 1741614701,
"rating": 1521,
"servers": ["us-east-1", "eu-west-1", "eu-central-1", "europe-north-1"]
},
{
"ticketId": "player6",
"joinTimestamp": 1741614702,
"rating": 1523,
"servers": ["us-east-1", "eu-west-1", "europe-north-1"]
},
{
"ticketId": "player11",
"joinTimestamp": 1741614703,
"rating": 1525,
"servers": ["us-east-1", "eu-central-1", "eu-west-1"]
},
{
"ticketId": "player12",
"joinTimestamp": 1741614704,
"rating": 1527,
"servers": ["us-east-1", "eu-west-1", "europe-north-1"]
},
{
"ticketId": "player13",
"joinTimestamp": 1741614705,
"rating": 1531,
"servers": ["asia-east-1", "europe-north-1", "us-west-1"]
}
]
}

This example contains 13 tickets with various ratings and servers that should be matched for a 2v2 game mode.

Response

{
"matchSuggestions": [
{
"id": "460e87cc-f851-4800-be31-0ddf3c41e6d2",
"teams": [
{
"id": "6748645d-2381-4061-8e05-e0921af5b22c",
"tickets": [
{
"ticketId": "player3",
"joinTimestamp": 1741614693,
"rating": 1501.0,
"servers": ["eu-central-1", "us-west-1", "asia-east-1"],
"exactEqualityConstraints": {}
},
{
"ticketId": "player8",
"joinTimestamp": 1741614696,
"rating": 1507.0,
"servers": ["eu-central-1", "asia-south-1", "eu-west-1"],
"exactEqualityConstraints": {}
}
]
},
{
"id": "925f359b-3945-416d-9768-c04abc551dcb",
"tickets": [
{
"ticketId": "player7",
"joinTimestamp": 1741614694,
"rating": 1503.0,
"servers": ["eu-central-1", "us-east-1"],
"exactEqualityConstraints": {}
},
{
"ticketId": "player2",
"joinTimestamp": 1741614695,
"rating": 1505.0,
"servers": ["eu-central-1", "us-west-1", "eu-west-1"],
"exactEqualityConstraints": {}
}
]
}
],
"server": "eu-central-1",
"matchScore": 6.0
},
{
"id": "6e72f7ef-e696-4fc0-812a-8e5c39eb2acb",
"teams": [
{
"id": "ac189a38-d144-454b-ba84-2eda4c473e21",
"tickets": [
{
"ticketId": "player5",
"joinTimestamp": 1741614697,
"rating": 1511.0,
"servers": ["eu-west-1", "us-east-1", "asia-north-1"],
"exactEqualityConstraints": {}
},
{
"ticketId": "player9",
"joinTimestamp": 1741614700,
"rating": 1517.0,
"servers": ["eu-west-1", "us-east-1", "asia-north-1"],
"exactEqualityConstraints": {}
}
]
},
{
"id": "71569c76-95e7-40b8-9d05-7eb07fd2e8fb",
"tickets": [
{
"ticketId": "player10",
"joinTimestamp": 1741614698,
"rating": 1513.0,
"servers": ["eu-west-1", "us-south-1", "us-east-1"],
"exactEqualityConstraints": {}
},
{
"ticketId": "player1",
"joinTimestamp": 1741614699,
"rating": 1515.0,
"servers": ["eu-west-1", "us-east-1", "asia-south-1"],
"exactEqualityConstraints": {}
}
]
}
],
"server": "eu-west-1",
"matchScore": 6.0
},
{
"id": "860987c3-34f4-44bf-82cd-73d9eac7251f",
"teams": [
{
"id": "10523853-5732-4025-89e8-777e723340e8",
"tickets": [
{
"ticketId": "player4",
"joinTimestamp": 1741614701,
"rating": 1521.0,
"servers": [
"us-east-1",
"eu-west-1",
"eu-central-1",
"europe-north-1"
],
"exactEqualityConstraints": {}
},
{
"ticketId": "player12",
"joinTimestamp": 1741614704,
"rating": 1527.0,
"servers": ["us-east-1", "eu-west-1", "europe-north-1"],
"exactEqualityConstraints": {}
}
]
},
{
"id": "6aaa3f96-9388-41f3-a666-06a0f9a59acd",
"tickets": [
{
"ticketId": "player6",
"joinTimestamp": 1741614702,
"rating": 1523.0,
"servers": ["us-east-1", "eu-west-1", "europe-north-1"],
"exactEqualityConstraints": {}
},
{
"ticketId": "player11",
"joinTimestamp": 1741614703,
"rating": 1525.0,
"servers": ["us-east-1", "eu-central-1", "eu-west-1"],
"exactEqualityConstraints": {}
}
]
}
],
"server": "us-east-1",
"matchScore": 6.0
}
],
"unmatchedTickets": [
{
"ticketId": "player13",
"joinTimestamp": 1741614705,
"rating": 1531.0,
"servers": ["asia-east-1", "europe-north-1", "us-west-1"],
"exactEqualityConstraints": {}
}
]
}

Note that there will always be one unmatchedTicket since 13 tickets cannot be fully distributed to a 2v2.

Now that you have seen two examples, it's time to check out the Customization for the self-hosted matchmaker.