> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/learn/server-browser.md).

# Server Browser

Get started with Server Browser quickly and explore example scenarios for various genres.

Server Browser is a managed service for [Deployments](/learn/orchestration/deployments.md#match-bound) and [Persistent](/learn/orchestration/persistence.md) servers:

* **help players search and join suitable servers** based on capacity, latency, or game parameters;
* **pre-warm new servers** to serve global audiences at scale and prevent frustrating queues;
* **streamline server operations** including updates, restarts, persistence, meshing, and more.

{% hint style="success" %}
Looking to match players based on strict rules, without allowing server choice? Consider [Matchmaking](/learn/matchmaking.md).
{% endhint %}

## ✔️ Preparation

**Testing this service is entirely free, no credit card required.**

Free Tier allows up to 3 hours of runtime on our shared test cluster, after each restart.

This tutorial assumes you have already:

* [understood Edgegap’s deployment model](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-just-in-time-deployment-dedicated-servers),
* published your server application on Edgegap ([Unreal Engine](/unreal-engine.md), [Unity](/unity.md)),
* successfully connected from a game client to your server on Edgegap.

### Functions and Flow

<figure><img src="/files/i2b0y0S5ani1sv4p4brp" alt=""><figcaption><p>Server Browser: Flow and Hierarchy</p></figcaption></figure>

Server Browser offers two main features:

[#start-browsing](#start-browsing "mention") with client and server integration:

* Clients reserve seats with a single method and receive connection details.
* Clients may browse suitable servers and slots using custom filters (in-game UI).
* Servers authenticate player connections in servers using [Federated Identity](#user-content-fn-1)[^1].
* Servers update slot capacity and metadata to modify visibility or trigger scaling.

[#automated-scaling](#automated-scaling "mention") (optional feature) with Scaling Policies:

* Monitor available server instances, slots, capacity - per region or custom criteria.
* Deploy new Servers to increase capacity with prewarming or just-in-time scaling.
* Automate operations with isolated policies for limited time events (QA test, tournament).

{% hint style="info" %}
After game release, **your server browser needs to run 24/7** to ensure players across the world can join servers.
{% endhint %}

## ▶️ Start Browsing

Learn about server and player (client) lifecycle to ensure efficient server use.

### Authenticate

All requests must send an `Authorization`  HTTP header with your secret **Auth Token:**

<pre><code>Authorization: <a data-footnote-ref href="#user-content-fn-2">xxxxxxxx-e458-4592-b607-c2c28afd8b62</a>
</code></pre>

{% hint style="warning" %}
**Keep your tokens secret and safe! Edgegap staff will never ask you for your tokens.**
{% endhint %}

{% hint style="info" %}
Matchmaker token and Server Browser tokens are separate from Edgegap API tokens.
{% endhint %}

Server Browser automatically generates two types of tokens:

* **Server Token** - required for [Server API](#server-lifecycle) methods, can be [injected as app version variable](/learn/orchestration/application-and-versions.md#injected-variables).
  * Grants access to all API methods, handy for testing, devops, or custom scaling.
* **Client Token** - required for [Monitor API and Seat Reservation API](#player-lifecycle) used by Game Clients.

{% hint style="success" %}
Store Client Token in a game backend secret store to make token rotation easier in production.
{% endhint %}

### Discover Instance

Discovery is the process where a fully initialized server notifies Server Browser and becomes visible [through search or auto-assigned reservations](#allocate-capacity).

{% hint style="warning" %}
**New** [Deployments](/learn/orchestration/deployments.md) **must create a new Instance** when initialized to keep track of added capacity.
{% endhint %}

{% hint style="info" %}
See [#automated-scaling](#automated-scaling "mention") to learn about Scaling Policies and start deployments automatically.
{% endhint %}

**Required information** for each server instance includes:

* at least one Slot defined when initializing instance,
* server connection details - URL, IP, port information, and location.

**Optional custom metadata parameters** for player filtering, sorting, and browsing; for example:

* slot information - team capacity and team-specific metadata (e.g. team name),
* name and tags - customizable, unique, human-readable and searchable labels;
* compatibility data - server version or supported client versions;
* latency qualifiers - city and region identifiers, and assigned [Ping Beacons](/learn/orchestration/ping-beacons.md) details;
* game parameters - level/scene/map, game mode, difficulty, mods used;
* any other custom parameters to help players filter and find a suitable server.

{% hint style="info" %}
Metadata parameters above are just examples, you can define any number of parameters as needed.
{% endhint %}

{% hint style="success" %}
To serialize nested objects, encode their accessor path in the key as `"object.child.property"`.
{% endhint %}

Servers may **update instance or slot metadata anytime** to modify their discoverability criteria. When updating metadata, all indexed keys must be provided with valid values (even if not modified).

**Server Instances must periodically send a keep-alive heartbeat** to verify their ongoing availability and prevent players from joining crashed or offline servers. Missing heartbeat for the configured expiration period will automatically delete instance and any pending seat reservations.

{% hint style="info" %}
See [Persistence](/learn/orchestration/persistence.md) for managing persistent world state and [Apps and Versions](/learn/orchestration/application-and-versions.md#active-caching) for faster deployments.
{% endhint %}

### Allocate Capacity

Instance and Slot capacity may be allocated in two ways, used individually or together:

* [#auto-assigned-reservation](#auto-assigned-reservation "mention") to choose a server started with a specific scaling policy,
* [#search-and-browse](#search-and-browse "mention") lets the player set filters and browse suitable servers to pick by hand.

{% hint style="success" %}
We recommend getting started with [#auto-assigned-reservation](#auto-assigned-reservation "mention") as the simpler option.
{% endhint %}

#### Auto-Assigned Reservation

{% hint style="info" %}
Implement auto-assignment to **choose server automatically**, based on player region.
{% endhint %}

Players may create an auto-assigned reservation, only supplying player IDs and a scaling policy name. Server Browser will automatically find an instance slot offering sufficient joinable capacity and reserve seats, responding immediately with instance connection details.

If there is no suitable instance slot for this reservation, the response:

* **status code indicates policy scaling state** and if more capacity is being added,
* if scaling up, **header `Retry-After`  indicates wait period (seconds) before retrying**.

Once a reservation is completed, you may skip to [#connect-to-server](#connect-to-server "mention").

#### Search and Browse

{% hint style="info" %}
Implement custom search for **custom filter criteria and/or to show players a list of servers.**
{% endhint %}

Players may list server instances with custom filters and sorts, and [paginate results](#pagination) to find a server they'd like to join. Each Instance's Slots may be searched using the same approach.

Instances and slots may be filtered and sorted with built-in parameters or [indexed metadata](#configuration):

<table><thead><tr><th width="400">Property</th><th width="140">Data Type</th><th width="105">Instance</th><th width="105">Slot</th></tr></thead><tbody><tr><td><code>request_id</code></td><td><code>string</code></td><td>✅</td><td>❌</td></tr><tr><td><code>total_joinable_seats</code>, <code>total_available_seats</code></td><td><code>int</code></td><td>✅</td><td>❌</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>❌</td><td>✅</td></tr><tr><td><code>available_seats</code>, <code>reserved_seats</code></td><td><code>int</code></td><td>❌</td><td>✅</td></tr><tr><td><code>created_at</code>, <code>updated_at</code></td><td><code>string</code></td><td>✅</td><td>✅</td></tr><tr><td><code>metadata.{index}</code> (custom)</td><td><code>string</code>, <code>int</code>, <code>float</code>, <code>bool</code></td><td>✅</td><td>✅</td></tr></tbody></table>

Available filtering operators depend on the data type of the filtered property:

<table><thead><tr><th width="125">Parameter</th><th width="135">Operators</th><th>Example Filter (based on Simple Example)</th></tr></thead><tbody><tr><td><code>string</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  or <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  or<br><code>contains</code>  or<br></p></td><td><pre><code>?$filter=metadata.custom_name contains 'my game'
and metadata.server_version le '1.1.0'
and metadata.server_version ge '1.0.0'
&#x26;$order=metadata.custom_name asc
</code></pre></td></tr><tr><td><code>string</code></td><td>literal values<br><code>in</code>  (filter)<br><code>rank</code>  (sort)</td><td><pre><code>?$filter=metadata.city in ('Chicago', 'Toronto')
&#x26;$order=rank(metadata.city, 'Chicago', 'Toronto')
</code></pre></td></tr><tr><td><code>int</code>, <code>float</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  or <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  </p></td><td><pre><code>?$filter=metadata.xp_multiplier gt 1.0
&#x26;$order=metadata.xp_multiplier desc
</code></pre></td></tr><tr><td><code>bool</code></td><td><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a></td><td><pre><code>?$filter=metadata.allows_new_connections eq true
</code></pre></td></tr></tbody></table>

{% hint style="success" %}
Filter and sort by custom `metadata.city`  for best latency after measuring with [Ping Beacons](/learn/orchestration/ping-beacons.md).
{% endhint %}

{% hint style="info" %}
Learn about cursor-based [#pagination](#pagination "mention") to let users fetch more results.
{% endhint %}

#### Reserve Seats

**Before joining a server, players must reserve seats** to ensure that the instance offers sufficient available capacity and prevent overcrowding. Reservations can include a group of players or a solo individual.

Federated Identity: Players must provide a unique third party player ID in their reservation. Once they [#connect-to-server](#connect-to-server "mention"), send the same ID for server-side verification.

Once a reservation is made successfully, players should attempt connecting immediately. Pending **reservations expire after 30s (configurable) unless confirmed** by server.

**Reservations exceeding slot's joinable seat capacity are rejected** ([409 Conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/409)). Joinable seats are any available seats which have not been reserved by other players yet.

### Connect to Server

As soon as the seat reservation is made, **players should proceed to connect to your deployment's game server and pass their player ID with a netcode RPC**.

{% tabs %}
{% tab title="Unreal Engine" %}
To **connect from PIE (Editor)** during development and testing, press the tilde key `~`  and type `open {URL}:{port}` and wait for your editor to load the map.

{% hint style="success" %}
In case of failed connections or dark screen consult our [troubleshooting guide](/unreal-engine.md#troubleshooting-and-faq-1).
{% endhint %}
{% endtab %}

{% tab title="Unity" %}
To **connect your Unity Editor** or **game client** to your cloud deployment, input:

* **Deployment** **URL** pointing to the server's IP, usually in `NetworkManager`  component.
* **External port** mapping to the [server's internal listen port](https://docs.edgegap.com/learn/advanced-features/application-and-versions#port-mapping), usually in a Transport component.

{% hint style="success" %}
In case of connection timeout or other issues consult our [troubleshooting guide](/unity.md#troubleshooting-and-faq-4).
{% endhint %}
{% endtab %}
{% endtabs %}

To authenticate new connections, **your server must send a bulk reservation confirmation** request with all new players' IDs, receiving in response:

* assignment of accepted player reservations to their preferred slot,
* assignment of expired player reservations to their preferred slot,
* a list of unknown player IDs.

Your **server decides how to handle expired group of players** and whether to allow or kick/ban expired or rejected users. Each **instance slots must be updated immediately with new available seat count** to ensure future reservations won't exceed slot capacity.

{% hint style="info" %}
Your server owns the authority to change any slot's capacity, add, delete, or update any slots - removing all reservations for this slot if pending reservations exceed available seats.
{% endhint %}

### Abandon Server

When players leave, your **server must increase available seats for the assigned slot**.

{% hint style="success" %}
If your server allows a reconnection period, your server may wait before updating slots.
{% endhint %}

Read about [Persistence](/learn/orchestration/persistence.md#recovery-objectives) to prevent frustrating persistent server rollbacks.

## 🚀 Automated Scaling

Server Browser is compatible with several different methods of autoscaling:

* **pre-warming method** - starting servers strictly with Server Browser scaling policies,
* **just-in-time method** - start through [Matchmaking](/learn/matchmaking.md) and [fill with Server Browser](#allocate-capacity),
* **custom autoscaler** - start through custom game backend and [fill with Server Browser](#allocate-capacity).

The following guide will focus on **pre-warming with Scaling Policies**.

<figure><img src="/files/FQbGMvDJSeKhT1Atqfib" alt=""><figcaption><p>Server Browser Scaling Policies UI</p></figcaption></figure>

{% hint style="success" %}
Stop deployments in [Unreal Engine](/unreal-engine.md#stop-deployments), [Unity](/unity.md#stop-deployments), or [with API](/docs/api/dedicated-servers.md#delete-v1-self-stop-request_id-access_point_id) to control server cost reliably.
{% endhint %}

### Monitor Capacity

Server Browser will refresh the list of discovered instances every [`monitoring_interval`](#user-content-fn-9)[^9] .

{% hint style="warning" %}
To **prevent overscaling**, set monitoring interval slightly higher than avg server startup time.
{% endhint %}

{% hint style="info" %}
Each policy should use a filter (using [filtering syntax](#search-and-browse)) to monitor regional capacity.
{% endhint %}

Your configured [`minimum_active_instances`](#user-content-fn-10)[^10]  amount can be treated either as a:

* **fixed capacity** of deployments that you wish to keep running at all times,
* **pre-warm standby** deployment buffer to hide initialization delays.

#### Fixed Capacity

Fixed capacity policies should not use seat related filters.

This type of policy configuration is most often used for Quality Assurance, Tournaments, Closed Alphas, Publisher Demos, or other limited capacity events.

Alternatively, games with [Persistence](/learn/orchestration/persistence.md) usually want to maintain long running servers, particularly when offering players to provision [Persistence](/learn/orchestration/persistence.md#community-servers).

{% hint style="info" %}
Scaling policy helps you automatically restart and recycle crashed servers on the fly.
{% endhint %}

#### Pre-Warm Standby

Pre-warm policies should use joinable seat filters to monitor capacity usage.

Start standby idle servers to stay ahead of player demand if:

* you're launching globally and expect rapid inflow of players in a short timespan,
* or server initialization requires more than 30 seconds ([not including deployment time](#user-content-fn-11)[^11]),
* or servers utilize meshing strategies requiring more complex network topology.

### Deploy Servers

New deployments are started automatically when the amount of discovered instance*s* drops below the policy's minimum of active instances. Deployments are retried infinitely every monitoring interval after [`deployment_registration_period`](#user-content-fn-9)[^9]  elapsed.

{% hint style="warning" %}
**Make sure to verify your** [**server auto-discovery**](#discover-instance) **integration with your policy filter, otherwise your policy may loop infinitely and create large amounts of unused deployments!**
{% endhint %}

{% hint style="info" %}
Deployments may use [Private Fleets](/learn/orchestration/private-fleets.md) (with Overflow to Cloud) or Cloud directly.
{% endhint %}

Available parameters include ([see full API specification](/docs/api/dedicated-servers.md#private-fleets)):

* [**application and version**](/learn/orchestration/application-and-versions.md) - build version, resources, and other orchestration parameters,
* **users** - a single set of geographical coordinates for preferred [server placement](/learn/orchestration/deployments.md#regional-standby),
* [**private host IDs**](/learn/orchestration/private-fleets.md) - leave empty for cloud, or specify hosts in desired region,
* [**tags**](/learn/orchestration/deployments.md#dashboard-monitoring) - tag with policy name to find deployments started with this policy later on,
* [**environment variables**](/learn/orchestration/deployments.md#custom-variables) - pass custom parameters and secrets to servers,
* [**webhooks**](/learn/orchestration/deployments.md#webhooks-and-postbacks) - notify your game backend (or matchmaker) of deployment lifecycle events,
* [**require cached locations**](/learn/orchestration/application-and-versions.md#active-caching) - if you prefer faster deployments only in cached locations.

### Example Policies

Test and modify any of these policies as you need. Most games will use multiple policies.

{% tabs %}
{% tab title="🍀 QA Instance" %}
A simple policy to keep one server deployed for testing at all times.

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-qa-pool",
<strong>  <a data-footnote-ref href="#user-content-fn-12">"filter"</a>: "metadata.policy_name eq 'sb-qa-pool'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-qa-pool"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-qa-pool",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  "minimum_active_instances": 1
</strong>}
</code></pre>

{% endtab %}

{% tab title="🌡️ Pre-Warm Region" %}
Start 10x deployments in anticipation of demand. Copy per region.

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-v1.0.0-chicago",
<strong>  <a data-footnote-ref href="#user-content-fn-16">"filter"</a>: "total_joinable_seats gt 0 and metadata.policy_name eq 'sb-v1.0.0-chicago'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-v1.0.0-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-v1.0.0-chicago",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  <a data-footnote-ref href="#user-content-fn-17">"minimum_active_instances"</a>: 10
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔒 MMO" %}
Add deployments if available capacity drops below a threshold. Copy per region.

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-mmo-chicago",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-18">"filter"</a>: "total_joinable_seats gt 5 and metadata.policy_name eq 'sb-mmo-chicago'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-mmo-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-mmo-chicago",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
<strong>  "minimum_active_instances": 3
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔑 Community" %}
One policy per server owner, passing custom owner-defined password.

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-owner-jnjnc8mid",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-21">"filter"</a>: "metadata.policy_name eq 'sb-owner-jnjnc8mid'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["community", "sb-owner-jnjnc8mid"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-owner-jnjnc8mid",
</strong>        "is_hidden": false
      },
      {
<strong>        "key": "SB_SERVER_PASSWORD",
</strong><strong>        "value": "password1234",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-22">"webhook_on_ready"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-23">"webhook_on_error"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
  "minimum_active_instances": 1
}
</code></pre>

{% endtab %}
{% endtabs %}

## ⚙️ Configuration

Server Browser API is generated using your JSON configuration at Server Browser start. You may specify custom expiration/registration time windows, and custom metadata:

{% tabs %}
{% tab title="🍀 Simple Example" %}

<pre class="language-json" data-title="sb-simple-example-v1-1-0.json"><code class="lang-json">{
	"version": "1.1.0",
	"server_instances": {
		"expiration_period": "1m",
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"policy_name": "string",
			"name": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "1m"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="🎈 Social Games" %}

<pre class="language-json" data-title="sb-social-example-v1-1-0.json"><code class="lang-json">{
	"version": "1.1.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"seed": "string",
			"max_players": "int",
			"app_version": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_latency": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="🤝 Cooperative Games" %}

<pre class="language-json" data-title="sb-cooperative-example-v1-1-0.json"><code class="lang-json">{
	"version": "1.1.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"avg_rank": "int",
			"max_players": "int",
			"app_version": "string",
			"tags": "string",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="⚔️ Competitive Games" %}

<pre class="language-json" data-title="sb-competitive-example-v1-1-0.json"><code class="lang-json">{
	"version": "1.1.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"avg_rank": "int",
			"max_players": "int",
			"is_ranked": "bool",
			"app_version": "string",
			"cpu_frequency": "int",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-24">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_rank": "int",
			"avg_latency": "int"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "15s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }

</code></pre>

{% endtab %}
{% endtabs %}

{% hint style="info" %}
For best performance, omit indices which are not used for filtering or sorting. Non-indexed params can be still set and read with API methods or engine-specific SDK.
{% endhint %}

## ☁️ Hosting Cluster

Server Browser is conveniently hosted and managed 24/7 around the clock by Edgegap.

Choose a hosting option best suited for your goal:

* **Free Cluster (shared)** to test all features and explore synergies with your design,
  * shuts down after 3 hours automatically, requiring restart to continue testing.
* **Private Cluster** **(dedicated)** to ensure a stable environment for your production needs,
  * pick your region and get 24/7 support for live games to release with confidence.

#### Private Cluster Tiers

We currently offer [3 private cluster tiers](https://edgegap.com/resources/pricing#managed-infrastructure) to cater to everybody’s needs:

<table><thead><tr><th width="160">Tier</th><th align="right">Hobbyist Tier</th><th align="right">Studio Tier</th><th align="right">Enterprise Tier</th></tr></thead><tbody><tr><td>Best Suited For</td><td align="right">enthusiasts,<br>solo developers</td><td align="right">commercial releases</td><td align="right">high-traffic launches</td></tr><tr><td>Resources</td><td align="right">1 vCPU + 2GB RAM</td><td align="right">6 vCPU + 12GB RAM</td><td align="right">18 vCPU + 48GB RAM</td></tr><tr><td>Redundancy</td><td align="right">1x virtual node</td><td align="right">3x virtual nodes</td><td align="right">3x virtual nodes</td></tr><tr><td>Rate Limit (req/s)</td><td align="right">200</td><td align="right">750</td><td align="right">2,000</td></tr><tr><td>Price, hourly</td><td align="right">$0.0312</td><td align="right"> $0.146</td><td align="right">$0.548</td></tr><tr><td><strong>Price, 30 days</strong><br>(nonstop usage)</td><td align="right"><strong>$22.464</strong></td><td align="right"><strong>$105.12</strong></td><td align="right"><strong>$394.56</strong></td></tr></tbody></table>

🌟 Upgrade free instances to private cluster with one click and get highly available hosting maintained by Edgegap team with 24/7 live support for publicly released games.

Resource requirements for your instance will depend on factors:

* **number of players,** more players ⇒ more API requests and higher CPU usage,
* **requests per player,** more frequent retries ⇒ higher CPU resource usage,
* **number of servers,** more servers ⇒ higher CPU usage and memory usage,
* **client retry fallback logic** - retrying without jittered backoff ⇒ [thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem),
* **average match duration** - shorter sessions ⇒ higher frequency of lifecycle events.

{% hint style="info" %}
Our clusters use cloud machines featuring AMD/Intel CPUs with clock speed 2.4 - 3.2 GHz.
{% endhint %}

## 📗 API

**Consider our SDKs for** [**Unreal Engine**](/unreal-engine/developer-tools.md) **or** [**Unity**](/unity/server-browser.md) **to get started with prebuilt examples.**

{% hint style="info" %}
Unity/Android - consider [using raw string interpolation](https://www.c-sharpcorner.com/article/convert-string-to-json-in-c-sharp/) to prevent code stripping of hardcoded JSONs.
{% endhint %}

{% hint style="success" %}
**Swagger Web UI**: deploying your managed service generates an openAPI specification and a convenient web UI, useful for testing edge cases or validating payload shapes.
{% endhint %}

{% file src="/files/r7C86GrefAHySwSy51kj" %}

Import API specification to [Scalar API Web Client](https://client.scalar.com/workspace/default/request/default) or [Swagger Editor](https://editor.swagger.io/) to inspect details.

### Rate Limits

To protect your instace from exceeding burst capacity and crashing, we limit the number of client requests per second, per client's public IP address.

The limit is configured by [#configuration](#configuration "mention") parameter `rate_limits.per_client_ip`.

{% hint style="warning" %}
If your game clients do not retry requests upon receiving response `429 Too Many Requests` **some players may be unable to join servers** during short bursts and peak traffic periods.
{% endhint %}

{% hint style="success" %}
**We recommend testing app behavior with lower rate limits during development (1 req/s).**
{% endhint %}

#### Load Testing

Load testing in production-like environment comes with a deployment hosting cost. See resources and prices associated with each tier on [our pricing page](https://edgegap.com/resources/pricing#matchmaker).

{% hint style="warning" %}
**Use** [**private clusters**](#private-cluster-tiers) **for stress testing.** Free instances are strictly limited for dev testing only.
{% endhint %}

When designing your load test, **please consider realistic player patterns**:

| Realistic Scenario                                                                           | Unrealistic Traffic Pattern                                                              |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| ✅ Players join game gradually, increasing req/s over several hours.                          | ❌ All players coordinate and hit the API in the exact same second.                       |
| ✅ Players wait an increasing amount of time between their retries (e.g. 1s-5s-10s-10s).      | ❌ All players retry immediately upon receiving `429 Too Many Requests`  response.        |
| ✅ Most players will receive their assignments within a short time (10-60s) and stop polling. | ❌ All players continue polling for a set amount of time even after receiving assignment. |
| ✅ Most players finish their game (taking time) before restarting a new session.              | ❌ All players restart their session anew immediately after receiving server assignment.  |
| ✅ Peak traffic is sustained for \~6 hours a day, after which some time zones drop off.       | ❌ Peak traffic is sustained 24 hours a day, with all players playing night and day.      |

#### Behavior Under Load

**Client requests** - if any client reaches the configured per-IP req/s rate limit, they receive `429 Too Many Requests`  and should back off (wait) before retrying.

**Deployment requests** - if scaling policies trigger more deployments than the allowed req/s limit of your organization, your server browser will retry automatically every monitoring interval, using a weighted round-robin strategy between all instance policies.

Policy weights are derived from the amount of planned deployments in each round, distributing the available deployment quota between all scaling policies evenly.

### Pagination

**Server Browser provides cursor pagination to fetch filtered results incrementally in a specific order.** This approach requires sending a cursor (starting point) and a page size (number of response items) whenever fetching more results, as opposed to the traditional limit-offset pagination.

{% hint style="info" %}
Cursor pagination combined with our metadata instance indexing system, provides the most consistent yet flexible user experience for filtering highly dynamic data.
{% endhint %}

The main priority is for users to find a suitable server on the first page.

For best experience, we recommend showing cached results for previous pages, and only refreshing results when the user clicks Search or manually requests a refresh.

## 🔖 Changelog

#### Semantic Versioning

Our developer tools and managed services use official [Semantic Versioning](https://semver.org/), indicating which updates are ✅ safe (minor, patch) and which may contain ⚠️ breaking changes (major).

**Once a version is released, it will never be modified/changed**.

{% hint style="info" %}
**The latest version of server browser is `1.1.0`** . Keep an eye out for [updates and announcements](/docs/release-notes.md).
{% endhint %}

[^1]: third party player identifiers

[^2]: example value

[^3]: equals

[^4]: not equals

[^5]: lower than

[^6]: lower than or equal

[^7]: greater than

[^8]: greater than or equal

[^9]: see Configuration

[^10]: see Example Policies

[^11]: use active caching to reduce deployment times easily

[^12]: * fixed capacity
    * assumes instances supply policy name in metadata from injected variable

[^13]: replace with your own application name

[^14]: replace with your own application version

[^15]: Chicago coordinates

[^16]: * deploys when less than 10 joinable instances found
    * assumes instances supply policy name in metadata from injected variable

[^17]: we expect at least 10 deployments in the Chicago region

[^18]: * deploys when less than 3 instances with 5 or less joinable seats are found
    * assumes instances supply policy name in metadata from injected variable

[^19]: prefer private fleet if capacity available

[^20]: notify game backend when server restarts

[^21]: * does not monitor capacity
    * assumes instances supply policy name in metadata from injected variable

[^22]: notify game backend when ready

[^23]: notify game backend when deployment fails

[^24]: indices contain your custom metadata parameters used for filtering or sorting
