Introduction
ServerSpawn™ allows game developers and platforms to instantly provision, manage, and tear down dedicated BisectHosting game servers remotely on the BisectHosting Global Network, whether you need one server per match, lobby, or play session. Once a server is live, fetch and modify its configuration and runtime state through the Starbase™ panel UI or headlessly via the Starbase™ API (Headless API). The Client API endpoints documented below cover power, console, players, and more.
Audience: server-side / backend engineers. Every call is a server-to-server HTTP request authenticated with an API key. Do NOT ship an API key in a game client.
Dynamic Values
A couple of values are environment-specific and are intentionally left as placeholders throughout this guide. BisectHosting will provide the concrete values for your environment:
| Placeholder | Meaning |
|---|---|
<PANEL_HOST> | The panel host the API is served from, e.g. https://games.example.com. All paths below are relative to this host. |
<GAME_EGG_ID> | The numeric egg ID for your game image in your environment. Used as egg_id on every provision call. |
Region IDs are not hardcoded in this document on purpose; they differ per environment and can change. Always read them live from the regions discovery endpoint.
1Concepts & conventions
- Base path (ServerSpawn API):
<PANEL_HOST>/api/serverspawn/v1 - Base path (Client API):
<PANEL_HOST>/api/client - Transport: HTTPS, JSON.
SendContent-Type: application/jsonandAccept: application/jsonon every request. Do not use cookies or CSRF tokens. - Account-scoped: every server created with a given API key is owned by that key's user account.
The plan tier and billing context are fixed by BisectHosting (you do not choose them) and usage is billed automatically. match_idis your handle: you supply your own identifier (your match/session ID) onprovision, and you use it again to look up or tear down that server.
You never need to persist our internal server ID, but you can also use the returneduuidif you prefer.
match_id rules (read this)
- You choose it. Allowed characters: ASCII letters, numbers, dashes, and underscores (
alpha_dash), 1-128 chars. - It is single-use, forever, per account. Every provision attempt, successful or failed, permanently records your
match_id. You may not reuse amatch_id, even after the server has been deprovisioned. Generate a fresh, uniquematch_idfor every provision call (a UUID, or your own guaranteed-unique key).
uuid vs match_id
provision and status return a uuid (the canonical server identifier). Either identifier works:
- The ServerSpawn API (
/status,/deprovision) acceptsmatch_idoruuid; supply one, not both. If you supply both, it will result in an API error. - The Client API addresses a server by
uuidin the path (/api/client/servers/{uuid}). The fulluuidreturned here plugs in directly.
2Authentication
2.1 · Enable ServerSpawn access (one-time)
API keys are ordinary account API keys, but the ServerSpawn endpoints are gated behind a per-account flag. A BisectHosting admin enables “ServerSpawn Access” on the account you'll integrate with. Until that is on, every /api/serverspawn/v1/* call returns:
403 Forbidden
{ "errors": [ { "code": "AccessDeniedHttpException", "status": "403", "detail": "You do not have server spawn access." } ] }Ask your BisectHosting contact to enable ServerSpawn Access on your integration account before you start.
2.2 · Create a new API key
- Sign in to the panel at
<PANEL_HOST>with the integration account. - Open Account → API Credentials.
- Click Create, give it a description (e.g.
game-prod), and, recommended, set Allowed IPs (a newline-separated allowlist of your backend's egress IPs). Leave blank to allow any IP. - The full key is shown exactly once. Copy it now and store it in your secrets manager. It looks like:
ptlc_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (A 16-character ptlc_… identifier immediately followed by a 32-character secret; a 48-character token in total.)
You may hold up to 25 keys per account. Keys can be revoked any time from the same screen.
Programmatic creation is also possible (POST /api/client/account/api-keys), but it itself requires an existing key/session, so use the panel UI to mint your first key.
2.3 · Send the key
Every request carries the key as a bearer token:
Authorization: Bearer ptlc_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX2.4 · Rate limit
The ServerSpawn API shares the Client API limiter: 720 requests/minute per account (keyed by account). Exceeding it returns 429 Too Many Requests. Provisioning is far below this ceiling in practice; the limit mainly matters if you poll /status aggressively, so poll at a sane interval (e.g. every 2 to 5s).
3ServerSpawn API endpoints
Three simple endpoints make up the ServerSpawn API:
| Method | Path | Purpose |
|---|---|---|
| POST | /api/serverspawn/v1/provision | Create a server for a match. |
| GET | /api/serverspawn/v1/status | Look up a server's lifecycle status. |
| POST | /api/serverspawn/v1/deprovision | Safely delete (terminate) a server. |
Across all three endpoints, business errors return a JSON envelope with a machine-readable code and a human-readable error message (plus match_id when known). Shared codes include server_not_found (404) and unexpected_error (500); validation problems return 422 with the field-level errors array instead. Per-endpoint codes are listed with each endpoint below.
3.1 · Provision a server
/api/serverspawn/v1/provision Creates a server synchronously: BisectHosting selects a viable node in your requested region, reserves networking, creates the server, and begins installation. On success you receive the server identity and connection details immediately, with status installing.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
match_id | string | Required | Your unique handle for this server. ASCII alpha_dash, 1-128 chars. Single-use (see match_id rules). |
region_id | integer | Required | A region ID from the regions endpoint. The server is placed on the best node in that region (with automatic fallback to the region's secondary). |
egg_id | integer | Required | The game image. Use <GAME_EGG_ID>. |
ram | integer | Required | Memory in MB. Range 1 to 49152 (48 GB). |
dedicated_ip | boolean | Required | true = dedicated IP for this server; false = shared IP (port-mapped). |
dedicated_port | integer | Optional | Only meaningful with dedicated_ip: true. A preferred starting port 1 to 65535, used only when the egg doesn't define its own default dedicated port (the egg default takes precedence). |
schedule_restarts | number[] | Optional | Hours of day (e.g. [4, 16]) at which to schedule automatic restarts. |
env_variables | object | Optional | Map of startup-variable overrides, {"KEY":"value"}. Keys must be variables defined on the egg (unknown keys are rejected). Applied on top of the egg defaults before first boot. |
Example request
curl -sS -X POST "<PANEL_HOST>/api/serverspawn/v1/provision" \
-H "Authorization: Bearer ptlc_XXXXXXXX..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"match_id": "match-7f3a9c2e",
"region_id": 12,
"egg_id": <GAME_EGG_ID>,
"ram": 8192,
"dedicated_ip": false,
"schedule_restarts": [5],
"env_variables": {
"SERVER_NAME": "Match 7f3a9c2e",
"MAX_PLAYERS": "60"
}
}' The env_variables keys above are illustrative. The exact variable names come from your game's egg; discover them per server via the Client API GET .../startup endpoint, or ask BisectHosting for the variable list. Unknown keys fail validation.
Success · 200 OK
{
"status": "installing",
"server": {
"uuid": "d3f1c8a0-7b2e-4a6c-9f10-2a3b4c5d6e7f",
"match_id": "match-7f3a9c2e",
"connection": { "ip": "203.0.113.42", "port": 7777 }
}
}statusis alwaysinstallingimmediately after a successful provision; the server then installs and boots asynchronously on the node. Poll/statusuntil it reportsreadybefore directing players to it.connection.ip/connection.portare the game server's address. (For a brand-new dedicated IP these may take a moment to finalize; re-read them from/statusif null.)
Error responses
Business errors share a consistent JSON envelope: a machine-readable code, a human-readable error message, and (when known) the match_id. For example:
{
"code": "no_location_in_region",
"error": "No available locations found in the specified region."
}| HTTP | code | When |
|---|---|---|
422 | n/a | Validation failed: a missing/invalid field, a reused match_id, or an env_variables key not defined on the egg. Returns the field-level errors array, not the envelope. |
400 | server_already_exists | A live server already exists for this match_id. Body includes match_id. |
400 | no_location_in_region | The region has no usable location. |
400 | no_location_with_capacity | No capacity anywhere for the request. |
400 | regions_exhausted | Region(s) were tried but no node could host the server (capacity / CPU / disk). |
403 | n/a | ServerSpawn Access (or the account's enterprise) is not enabled. Returns the errors array with “You do not have server spawn access.” |
500 | unexpected_error | Unexpected failure. Safe to retry with a new match_id. |
Retry guidance: because match_id is single-use, any retry of a failed provision must use a newmatch_id. Treat a 4xx/5xx as “this match_id is now spent.”
3.2 · Get status
/api/serverspawn/v1/status Returns the current lifecycle status of a server (or of an in-flight provision that hasn't produced a server yet). Identify the server by match_idor uuid (one is required).
Query parameters
| Param | Type | Required | Notes |
|---|---|---|---|
match_id | string | One of | The match_id you provisioned with. |
uuid | string | One of | The server uuid. |
Example
curl -sS "<PANEL_HOST>/api/serverspawn/v1/status?match_id=match-7f3a9c2e" \
-H "Authorization: Bearer ptlc_XXXXXXXX..." \
-H "Accept: application/json"Responses
Server exists · 200 OK (includes the server block + live connection details):
{
"status": "ready",
"server": {
"uuid": "d3f1c8a0-7b2e-4a6c-9f10-2a3b4c5d6e7f",
"match_id": "match-7f3a9c2e",
"connection": { "ip": "203.0.113.42", "port": 7777 }
}
}Provision in flight, server not created yet · 200 OK (no server block):
{ "status": "provisioning" }Nothing matches · 404 Not Found:
{
"code": "server_not_found",
"error": "No server or provision found for the supplied identifier."
}Status values
status | Meaning |
|---|---|
provisioning | Still selecting a node / creating the server. No server block yet. |
installing | Server created; game image installing on the node. |
starting | Container is starting up. |
ready | Running; safe to connect players. |
stopping | Shutting down. |
stopped | Not running. |
failed | Provision or install failed. |
terminated | Server has been deprovisioned (soft-deleted). |
Once installed, starting / ready / stopping / stopped reflect the live power state reported by the node daemon. If the node can't be reached, status falls back to stopped.
3.3 · Deprovision a server
/api/serverspawn/v1/deprovision Safely deletes a server: removes it from the node and soft-deletes the record. Identify it by match_idor uuid (one is required). Scoped to your account; you can only deprovision your own servers.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
match_id | string | One of | The server's match_id. |
uuid | string | One of | The server's uuid. |
Example
curl -sS -X POST "<PANEL_HOST>/api/serverspawn/v1/deprovision" \
-H "Authorization: Bearer ptlc_XXXXXXXX..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "match_id": "match-7f3a9c2e" }'Responses
Success · 200 OK:
{
"status": "terminated",
"server": { "uuid": "d3f1c8a0-7b2e-4a6c-9f10-2a3b4c5d6e7f", "match_id": "match-7f3a9c2e" }
}No matching live server · 404 Not Found (already deprovisioned, never existed, or not yours):
{
"code": "server_not_found",
"error": "No matching server was found to deprovision."
}Deletion failed · 500 Internal Server Error:
{
"code": "unexpected_error",
"error": "An unexpected error occurred while attempting to deprovision the server.",
"match_id": "match-7f3a9c2e"
} Deprovision is idempotent from your side: a second call for the same server returns 404 (it's already gone). Remember the match_id stays spent; you cannot re-provision under it.
4Related Client API endpoints
Once a server exists, manage it with the standard Client API (<PANEL_HOST>/api/client), using the same bearer token. Address a server by its uuid in the path: /api/client/servers/{uuid}/...
Read the full Client API Documentation for more details. Below is a short summary of the most important endpoints.
Regions
/api/client/locations/regions The source of truth for valid region_id values. Read this at integration time (and periodically) rather than hardcoding IDs.
curl -sS "<PANEL_HOST>/api/client/locations/regions" \
-H "Authorization: Bearer ptlc_XXXXXXXX..." -H "Accept: application/json"{
"object": "list",
"data": [
{
"object": "region",
"attributes": {
"id": 12, // use this as region_id
"name": "US East",
"slug": "us-east",
"created_at": "2025-08-05T16:53:45+00:00",
"updated_at": "2025-08-05T16:53:45+00:00"
}
}
// ... more regions
]
} The actual id / name pairs are environment-specific; the values above are an example shape only.
Server control & inspection
| Method | Path | Purpose |
|---|---|---|
| GET | /servers/{uuid} | Server details (limits, feature set, current state). |
| GET | /servers/{uuid}/resources | Live CPU / RAM / disk usage and power state. |
| POST | /servers/{uuid}/power | Power signal. Body: { "signal": "start" | "stop" | "restart" | "kill" }. Returns 204. |
| POST | /servers/{uuid}/command | Send a console command. Body: { "command": "..." }. |
| GET | /servers/{uuid}/websocket | Get short-lived credentials for the live console / log WebSocket. |
| GET | /servers/{uuid}/startup | List startup variables (the valid env_variables keys + current values). |
| PUT | /servers/{uuid}/startup/variable | Update one startup variable. |
Players
| Method | Path | Purpose |
|---|---|---|
| GET | /servers/{uuid}/player | Configured players. |
| GET | /servers/{uuid}/player/online | Currently online players. |
Some games expose additional, game-specific endpoints (for example, ARK exposes cluster management under /servers/{uuid}/ark/... for cross-server transfers). The available set depends on the egg; inspect GET /servers/{uuid} or ask BisectHosting for your game's feature list.
Account / keys
| Method | Path | Purpose |
|---|---|---|
| GET | /account/api-keys | List your API keys. |
| POST | /account/api-keys | Create a key ({ "description": "...", "allowed_ips": ["..."] }). The bearer token is the response's identifier immediately followed by meta.secret_token. |
| DELETE | /account/api-keys/{identifier} | Revoke a key. |
5Example end-to-end flow
A typical “spin up a match server, run it, tear it down” lifecycle:
1. (once) Get region IDs → GET /api/client/locations/regions
2. Provision a match server → POST /api/serverspawn/v1/provision { match_id, region_id, egg_id, ram, ... }
3. Poll until ready → GET /api/serverspawn/v1/status?match_id=... (until status == "ready")
4. Connect players to connection.ip:connection.port
5. (optional) Control in-match → POST /api/client/servers/{uuid}/command { "command": "..." }
6. Match over → deprovision → POST /api/serverspawn/v1/deprovision { match_id }The same flow in Bash and TypeScript:
Bash
HOST="<PANEL_HOST>"
KEY="ptlc_XXXXXXXX..."
AUTH=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Accept: application/json")
MATCH_ID="match-$(uuidgen | tr 'A-Z' 'a-z')" # unique per provision, never reuse
# 1. Provision
curl -sS -X POST "$HOST/api/serverspawn/v1/provision" "${AUTH[@]}" -d "{
\"match_id\": \"$MATCH_ID\",
\"region_id\": 12,
\"egg_id\": <GAME_EGG_ID>,
\"ram\": 8192,
\"dedicated_ip\": false
}"
# 2. Poll status until ready
until [ "$(curl -sS "$HOST/api/serverspawn/v1/status?match_id=$MATCH_ID" "${AUTH[@]}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')" = "ready" ]; do
sleep 3
done
echo "Server ready."
# 3. ... run the match ...
# 4. Deprovision
curl -sS -X POST "$HOST/api/serverspawn/v1/deprovision" "${AUTH[@]}" -d "{ \"match_id\": \"$MATCH_ID\" }"Node.js (TypeScript)
const HOST = "<PANEL_HOST>";
const KEY = process.env.BISECT_API_KEY!; // ptlc_...
const headers = {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
};
async function provisionMatch(matchId: string, regionId: number) {
const res = await fetch(`${HOST}/api/serverspawn/v1/provision`, {
method: "POST",
headers,
body: JSON.stringify({
match_id: matchId, // unique per call, never reuse
region_id: regionId,
egg_id: GAME_EGG_ID, // <GAME_EGG_ID>
ram: 8192,
dedicated_ip: false,
}),
});
if (!res.ok) throw new Error(`provision failed: ${res.status} ${await res.text()}`);
return res.json() as Promise<{ status: string; server: { uuid: string; match_id: string; connection: { ip: string | null; port: number | null } } }>;
}
async function waitUntilReady(matchId: string, { intervalMs = 3000, timeoutMs = 600_000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`${HOST}/api/serverspawn/v1/status?match_id=${encodeURIComponent(matchId)}`, { headers });
const body = await res.json();
if (body.status === "ready") return body.server;
if (body.status === "failed") throw new Error(`provision failed for ${matchId}`);
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error(`timed out waiting for ${matchId}`);
}
async function deprovisionMatch(matchId: string) {
await fetch(`${HOST}/api/serverspawn/v1/deprovision`, {
method: "POST",
headers,
body: JSON.stringify({ match_id: matchId }),
});
}
// usage
const matchId = `match-${crypto.randomUUID()}`;
await provisionMatch(matchId, 12);
const server = await waitUntilReady(matchId);
console.log(`Connect players to ${server.connection.ip}:${server.connection.port}`);
// ... later ...
await deprovisionMatch(matchId);6Available regions
Region IDs are environment-specific and may change, so this guide does not hardcode them. Fetch the current list from the Client API and use the returned id as your region_id:
<PANEL_HOST>/api/client/locations/regionsSee the Regions section above for the response shape. If you want a fixed mapping of friendly names → region IDs for your launcher, pull this once at deploy time and cache it; re-check periodically in case capacity is added.
7Game egg
The game image is selected by egg_id. For your game, use:
egg_id = <GAME_EGG_ID> BisectHosting will provide the concrete numeric ID for your environment. To customize a server's configuration (session name, player limits, passwords, ports, etc.), pass env_variables on provision. The set of valid keys is defined by this egg; discover them at runtime per server via:
GET <PANEL_HOST>/api/client/servers/{uuid}/startup Any env_variables key not defined on the egg is rejected with 422, so validate against /startup (or the list BisectHosting gives you) when building your provision payloads.
8Quick reference
Auth header (all calls):Authorization: Bearer ptlc_…
| Action | Call |
|---|---|
| List regions | GET/api/client/locations/regions |
| Provision | POST/api/serverspawn/v1/provision |
| Status | GET/api/serverspawn/v1/status?match_id=… |
| Deprovision | POST/api/serverspawn/v1/deprovision |
| Power | POST/api/client/servers/{uuid}/power · { "signal": "restart" } |
| Console command | POST/api/client/servers/{uuid}/command · { "command": "…" } |
| Live console (WS) | GET/api/client/servers/{uuid}/websocket |
| Startup variables | GET/api/client/servers/{uuid}/startup |
Remember:
- ServerSpawn Access must be enabled on the account (ask BisectHosting).
match_idis unique and single-use; generate a fresh one per provision, even after deprovision.provisionreturnsinstalling; poll/statusuntilreadybefore connecting players.- Rate limit: 720 req/min per account.








