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.
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
.
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 havenumPlayerPerTeam
- This identifies how many players should be in each team
Taken together, this defines a 2v2
mode.
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 ticketjoinTimestamp
- The time the ticket was created, i.e. typically the player started to look for a matchrating
- A numeric metric used to assess "soft" compatibility of ticketsservers
- A list of servers that the player could play on
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.
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.
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.