Server Creator
API reference

Server Creator API

Assemble ReHLDS, ReGameDLL, Metamod, AMX Mod X, non-Steam auth, anti-cheat and bots into a single archive — validated before a byte is transferred.

No key required Read-only https://api.counter-strike-boost.com

Overview #

Offer "create a CS 1.6 server" in your panel without scraping anything. You choose components, we mirror each one from its official release channel, validate that the combination can actually run, generate the config files that must reference the exact stack you picked, and stream you one archive.

GET/v1/server-builder/metaCatalog: components, layers, versions, rules
GET/v1/server-builder/components/{slug}One component + full per-version file manifest
GET/v1/server-builder/resolveValidate a stack, preview every file — no download
GET/v1/server-builder/fileRead one file out of a build before downloading
GET/v1/server-builder/downloadStream the finished archive (ZIP)
The archive is rooted at the HLDS root, not at cstrike/

Unzip it over the folder that contains your vanilla Steam cstrike directory — not inside it. ReHLDS ships engine files (hlds_linux, engine_i486.so) that live next to cstrike/. Extracting one level too deep produces a server that will not boot. Every path in resolve is relative to that root, which makes it easy to verify.

Builds are Linux-only; Windows binaries are stripped from every mirror.

Layers, versions and rules #

Three concepts explain every validation error you can get.

Layers

Every component sits in a layer. Five layers are exclusive — you cannot run two engines or two Metamods — and the rest accept any number of components.

LayerExclusiveWhat it is
engineyesThe HLDS engine itself (ReHLDS)
gamedllyesThe Counter-Strike game library (ReGameDLL_CS)
metamodyesThe plugin loader everything else hangs off
amxmodxyesThe AMX Mod X line (1.10 or 1.9)
authyesNon-Steam auth provider (Reunion, dProto)
modulenoAMX Mod X modules (ReAPI, CS module pack)
anticheatnoReAuthCheck, ReChecker, ReSemiclip
voicenoRevoice, VoiceTranscoder
botsnoYaPB, PODBot mm
csbnoOur own extras (auto-listing)
contentnoMap packs and other content

Selecting components and versions

components is a comma-separated list of slugs. Pin a version with slug@version; leave it off for that component's latest mirrored version.

TEXT
components=rehlds,regamedll,metamod-r,amxmodx        # all latest
[email protected],metamod-r,amxmodx      # engine pinned
components=                                          # omit entirely -> our recommended stack

Because we mirror from each project's official release channel, "latest" really is upstream's latest — not a snapshot we took months ago. Duplicated slugs are collapsed silently; order does not matter.

Rules

Components declare requires, conflicts and recommends relationships against each other; they ship in meta and in each component's detail so you can grey out impossible choices in your UI instead of discovering them at build time.

"requires" is satisfied by the layer, not the exact slug

A requires pointing at a component in an exclusive layer is satisfied by any component of that layer. "AMX Mod X requires metamod-r" really means "requires a Metamod", so a user who picked Metamod-P is not told to install a second one. recommends is advisory and never blocks a build.

Catalog #

GET /v1/server-builder/meta server:read auth optional

Everything you need to render a component picker: each component with its layer, description, license, homepage, mirrored versions and rules — plus the layer definitions themselves. Components with no mirrored version are omitted, so anything you see here is buildable right now.

Parameters

None. This endpoint takes no parameters.

Response fields

FieldTypeNotes
components[].is_default bool Part of our recommended stack. Pre-tick these in your UI — they are exactly what you get when components is omitted.
components[].selectable bool false means this component is never a choice: it is pulled into the build automatically by a bundles rule on something else. Do not render a checkbox for it — the AMX Mod X Counter-Strike module pack arrives this way with whichever AMX Mod X line is picked.
components[].sort_order integer Our display order (ascending). Also the load order used inside the build.
components[].rules object[] | null null when the component has no rules. Each entry is { rule, component, note } where rule is requires, conflicts, recommends or bundles. A bundles entry is informational: selecting the subject silently adds the other component, so you neither have to request it nor offer it. note is human-readable — show it as the reason a checkbox is disabled.
components[].versions object[] Mirrored versions, newest first. is_latest marks the one you get when you do not pin. size_bytes and file_count are the mirrored archive's, not the final build's.
layers object[] Layer key, display label, and whether it is exclusive. Drive your radio-vs-checkbox choice from exclusive.

A component that exists in our catalog but has never mirrored successfully is simply absent here, yet still known to resolve — which is why asking for it by name returns unavailable rather than unknown.

Request
cURL
curl "https://api.counter-strike-boost.com/v1/server-builder/meta"
JavaScript
const res  = await fetch(
  "https://api.counter-strike-boost.com/v1/server-builder/meta"
);
const data = await res.json();

if (!data.status) {
  throw new Error(data.error);   // stable machine-readable code
}
PHP
<?php
$body = file_get_contents(
    "https://api.counter-strike-boost.com/v1/server-builder/meta"
);
$data = json_decode($body, true);

if (empty($data['status'])) {
    throw new RuntimeException($data['error'] ?? 'request_failed');
}
Python
import requests

r = requests.get(
    "https://api.counter-strike-boost.com/v1/server-builder/meta",
    timeout=30,
)
r.raise_for_status()
data = r.json()
Response 200 OK

components[] trimmed to two of twelve entries

200 OK
{
  "archive_root": "HLDS root (the folder containing cstrike/)",
  "components": [
    {
      "description": "Reverse-engineered, bug-fixed and hardened HLDS engine. Drop-in replacement for the stock Valve engine — faster, far fewer crashes, and the base every modern CS 1.6 stack is built on.",
      "detail_url": "https://api.counter-strike-boost.com/v1/server-builder/components/rehlds",
      "homepage": "https://github.com/rehlds/ReHLDS",
      "is_default": true,
      "layer": "engine",
      "license": "GPL-3.0",
      "name": "ReHLDS",
      "rules": null,
      "selectable": true,
      "slug": "rehlds",
      "sort_order": 10,
      "versions": [
        {
          "file_count": 8,
          "is_latest": true,
          "released_at": "2026-05-18 13:36:52",
          "size_bytes": 3112930,
          "version": "3.15.0.896"
        }
      ]
    },
    {
      "description": "AMX Mod X module exposing the ReHLDS and ReGameDLL APIs to plugins.",
      "detail_url": "https://api.counter-strike-boost.com/v1/server-builder/components/reapi",
      "homepage": "https://github.com/rehlds/ReAPI",
      "is_default": true,
      "layer": "module",
      "license": "GPL-3.0",
      "name": "ReAPI",
      "rules": [
        { "component": "rehlds",    "note": "ReAPI exposes the ReHLDS API — it will not load on a stock engine.", "rule": "requires" },
        { "component": "regamedll", "note": "ReAPI exposes the ReGameDLL API.",                                   "rule": "requires" },
        { "component": "amxmodx",   "note": "ReAPI is an AMX Mod X module.",                                      "rule": "requires" }
      ],
      "selectable": true,
      "slug": "reapi",
      "sort_order": 60,
      "versions": [
        {
          "file_count": 1,
          "is_latest": true,
          "released_at": "2026-05-18 13:52:56",
          "size_bytes": 403612,
          "version": "5.29.0.358"
        }
      ]
    }
  ],
  "docs": "https://counter-strike-boost.com/developers",
  "layers": [
    { "exclusive": true,  "key": "engine",  "label": "Engine" },
    { "exclusive": true,  "key": "gamedll", "label": "Game library" },
    { "exclusive": false, "key": "module",  "label": "Modules" }
  ],
  "page_url": "https://counter-strike-boost.com/addons/server-builder",
  "platform": "linux",
  "status": true
}

Component detail #

GET /v1/server-builder/components/{slug} server:read auth optional

The same component record as in the catalog, but with the complete file manifest for every mirrored version — exact paths, byte sizes and the executable bit. Use it to show what a component actually installs, or to diff two versions.

Parameters

slug string path required

Component slug, e.g. reapi.

Response fields

FieldTypeNotes
versions[].files[].path string Path relative to the HLDS root — already remapped to where the file belongs in a real server, not where upstream's archive happened to put it.
versions[].files[].executable bool The file must be chmod +x after extraction. We set it in the archive even when upstream forgot to.
versions[].size_bytes integer Size of the mirrored upstream archive. Sum the files[].size values for the extracted footprint.

Manifests can be large — the AMX Mod X base is over 900 files. Request this per component on demand rather than eagerly for the whole catalog.

Request
cURL
curl "https://api.counter-strike-boost.com/v1/server-builder/components/reapi"
JavaScript
const res  = await fetch(
  "https://api.counter-strike-boost.com/v1/server-builder/components/reapi"
);
const data = await res.json();

if (!data.status) {
  throw new Error(data.error);   // stable machine-readable code
}
PHP
<?php
$body = file_get_contents(
    "https://api.counter-strike-boost.com/v1/server-builder/components/reapi"
);
$data = json_decode($body, true);

if (empty($data['status'])) {
    throw new RuntimeException($data['error'] ?? 'request_failed');
}
Python
import requests

r = requests.get(
    "https://api.counter-strike-boost.com/v1/server-builder/components/reapi",
    timeout=30,
)
r.raise_for_status()
data = r.json()
Response
200 OK
{
  "component": {
    "description": "AMX Mod X module exposing the ReHLDS and ReGameDLL APIs to plugins. Required by most modern CS 1.6 plugins.",
    "homepage": "https://github.com/rehlds/ReAPI",
    "is_default": true,
    "layer": "module",
    "license": "GPL-3.0",
    "name": "ReAPI",
    "rules": [
      { "component": "rehlds",    "note": "ReAPI exposes the ReHLDS API — it will not load on a stock engine.", "rule": "requires" },
      { "component": "regamedll", "note": "ReAPI exposes the ReGameDLL API.",                                   "rule": "requires" },
      { "component": "amxmodx",   "note": "ReAPI is an AMX Mod X module.",                                      "rule": "requires" }
    ],
    "slug": "reapi",
    "versions": [
      {
        "file_count": 1,
        "files": [
          {
            "executable": true,
            "path": "cstrike/addons/amxmodx/modules/reapi_amxx_i386.so",
            "size": 569620
          }
        ],
        "is_latest": true,
        "released_at": "2026-05-18 13:52:56",
        "size_bytes": 403612,
        "version": "5.29.0.358"
      }
    ]
  },
  "status": true
}
404 Not Found
{
  "error": "not_found",
  "status": false
}

Resolve a stack (dry run) #

GET /v1/server-builder/resolve server:read auth optional

The single most useful endpoint here. Runs exactly the same validation and assembly as the download, then returns the resulting file list instead of the bytes — so you can show a user precisely what they are about to get, and catch an impossible stack before moving 100 MB. Call it on every selection change.

Parameters

components string default recommended stack

Comma-separated slugs, optionally slug@version. Omit entirely to get our recommended stack (every is_default component at latest).

plugins string optional

Comma-separated slugs from the plugin directory. Each is compiled and folded into the same archive, already registered in plugins.ini. An unknown slug is skipped with a note, not an error.

optimized boolean default false

Accepts 1, true, yes or on. Ships configs tuned by us — rates, round flow, rcon hardening, vote thresholds, and per-component cvars that only appear when that component is in the build. Leave it off and every config file is byte-for-byte as its project ships it.

Response fields

FieldTypeNotes
build_hash string SHA-256 fingerprint of the whole selection — components, versions, plugins, optimize flag and the current config overlay. Identical inputs give an identical hash and a cache hit. Use it as your own cache key; when it changes, the archive really did change.
download_url string The exact download URL for this selection, built from your query string. Hand it to the user rather than assembling it yourself.
file_count / total_bytes integer Uncompressed totals. Show total_bytes before a download so nobody is surprised by 100 MB.
files[].from string Which producer emitted the file: a component slug, csb, csb-optimized, or plugin:{slug}.
files[].origin enum upstream — untouched, exactly as the project ships it. generated — we wrote it (it must reference your exact stack). edited — an upstream config we replaced because optimized=1. plugin — came from a directory plugin. Great material for a "what did you change?" view.
notes string[] | null Non-fatal remarks, e.g. "plugin not found, skipped: foo". null when there is nothing to say. Check it — a typo'd plugin slug reports here, not as an error.
components object[] What actually got selected, with resolved versions and licenses. Render this as the attribution/licence list for the build.
files[] has no stable order

Entries come out of an unordered map, so the order differs between two identical calls. Sort by path before displaying or diffing.

Only two files are always generated rather than copied: liblist.gam and cstrike/addons/metamod/plugins.ini. Both must name the exact components you chose, so they cannot come from upstream. With optimized=1 a handful of config files additionally show up as edited.

Request
cURL
curl "https://api.counter-strike-boost.com/v1/server-builder/resolve?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
JavaScript
const res  = await fetch(
  "https://api.counter-strike-boost.com/v1/server-builder/resolve?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
);
const data = await res.json();

if (!data.status) {
  throw new Error(data.error);   // stable machine-readable code
}
PHP
<?php
$body = file_get_contents(
    "https://api.counter-strike-boost.com/v1/server-builder/resolve?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
);
$data = json_decode($body, true);

if (empty($data['status'])) {
    throw new RuntimeException($data['error'] ?? 'request_failed');
}
Python
import requests

r = requests.get(
    "https://api.counter-strike-boost.com/v1/server-builder/resolve?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1",
    timeout=30,
)
r.raise_for_status()
data = r.json()
Response 200 OK

files[] trimmed to two of 1021 entries

200 OK
{
  "build_hash": "91a44f56e411ef19a20d94a4ceadcd3bb66e8aac30e053d406496df505ea65fe",
  "components": [
    { "homepage": "https://github.com/rehlds/ReHLDS",       "layer": "engine",  "license": "GPL-3.0", "name": "ReHLDS",          "slug": "rehlds",    "version": "3.15.0.896" },
    { "homepage": "https://github.com/rehlds/ReGameDLL_CS", "layer": "gamedll", "license": "GPL-3.0", "name": "ReGameDLL_CS",    "slug": "regamedll", "version": "5.30.0.814" },
    { "homepage": "https://github.com/rehlds/Metamod-R",    "layer": "metamod", "license": "GPL-3.0", "name": "Metamod-R",       "slug": "metamod-r", "version": "1.3.0.149"  },
    { "homepage": "https://www.amxmodx.org",                "layer": "amxmodx", "license": "GPL-3.0", "name": "AMX Mod X 1.10",  "slug": "amxmodx",   "version": "1.10.0-git5479" }
  ],
  "download_url": "https://api.counter-strike-boost.com/v1/server-builder/download?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1",
  "file_count": 1021,
  "files": [
    {
      "executable": false,
      "from": "amxmodx",
      "origin": "upstream",
      "path": "cstrike/addons/amxmodx/data/gamedata/common.games/entities.games/cstrike/offsets-cdeadhev.txt",
      "size": 821
    },
    {
      "executable": false,
      "from": "csb",
      "origin": "generated",
      "path": "cstrike/addons/metamod/plugins.ini",
      "size": 334
    }
  ],
  "notes": null,
  "optimized": true,
  "status": true,
  "total_bytes": 20478882
}

Preview one file #

GET /v1/server-builder/file server:read auth optional

Read a single text file out of a build without downloading the archive. Built for "show me what server.cfg will look like" — especially useful for showing what optimized=1 actually changed.

Parameters

path string required

Exact path as it appears in resolve's files[].path, e.g. cstrike/addons/metamod/plugins.ini.

components string default recommended stack

Same as resolve. The file is resolved against this selection, so pass the same one.

plugins string optional

Same as resolve.

optimized boolean default false

Same as resolve. Flipping it is exactly how you diff stock against tuned.

Response fields

FieldTypeNotes
binary bool true when the file is not displayable text (a .so, a bot graph). content is then an empty string — do not try to render it.
truncated bool true when the file exceeded 512 KB and content holds only the first 512 KB. size is still the real size.
origin enum Same vocabulary as resolve. generated and edited are the interesting ones — those are the files we are responsible for.

Errors

StatuserrorWhen
400 missing_path The path parameter was absent or empty.
404 not_found That path is not in this selection's build. Note the wording: the same path may well exist for a different components set.
422 invalid_selection The selection itself is impossible — see selection errors.

path is not a filesystem lookup. It is matched against the assembled entry map for your selection, so the only readable files are ones that selection legitimately produces — a traversal attempt is simply not a key in that map.

Request
cURL
curl "https://api.counter-strike-boost.com/v1/server-builder/file?path=cstrike/addons/metamod/plugins.ini&components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
JavaScript
const res  = await fetch(
  "https://api.counter-strike-boost.com/v1/server-builder/file?path=cstrike/addons/metamod/plugins.ini&components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
);
const data = await res.json();

if (!data.status) {
  throw new Error(data.error);   // stable machine-readable code
}
PHP
<?php
$body = file_get_contents(
    "https://api.counter-strike-boost.com/v1/server-builder/file?path=cstrike/addons/metamod/plugins.ini&components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
);
$data = json_decode($body, true);

if (empty($data['status'])) {
    throw new RuntimeException($data['error'] ?? 'request_failed');
}
Python
import requests

r = requests.get(
    "https://api.counter-strike-boost.com/v1/server-builder/file?path=cstrike/addons/metamod/plugins.ini&components=rehlds,regamedll,metamod-r,amxmodx&optimized=1",
    timeout=30,
)
r.raise_for_status()
data = r.json()
Response
200 OK
{
  "file": {
    "binary": false,
    "content": "; Metamod plugin list — generated by the CSB Server Creator\r\n; https://counter-strike-boost.com/addons/server-builder\r\n;\r\n; Load order matters: the auth provider runs before AMX Mod X so that\r\n; every client already has its final SteamID when admins are looked up.\r\n\r\n; AMX Mod X 1.10\r\nlinux addons/amxmodx/dlls/amxmodx_mm_i386.so\r\n",
    "executable": false,
    "from": "csb",
    "origin": "generated",
    "path": "cstrike/addons/metamod/plugins.ini",
    "size": 334,
    "truncated": false
  },
  "status": true
}
400 missing_path
{
  "error": "missing_path",
  "status": false
}

Download the archive #

GET /v1/server-builder/download server:read auth optional

Streams the finished server as a ZIP. Same parameters and same validation as resolve — if resolve returned 200, this returns bytes.

Parameters

components string default recommended stack

Identical to resolve.

plugins string optional

Identical to resolve.

optimized boolean default false

Identical to resolve.

Errors

StatuserrorWhen
422 invalid_selection Impossible stack — see below. Nothing is transferred.
422 empty_selection Every requested component resolved to nothing.
429 rate_limited Hourly build budget exhausted. retry_after is 600.
500 build_failed Assembly failed on our side. Transient — retry once, then report it.
503 builder_unavailable The build cache volume is not configured on this instance. Operator problem, not yours.

The filename carries the build hashcs16-server-{first 12 of build_hash}.zip — and the full hash is in X-CSB-Build-Hash. Compare it against the build_hash from resolve to confirm you got the build you previewed.

Be patient on a cold build. Identical selections are served from a cache and come back immediately; a first-time selection assembles hundreds of files and may run the compiler, so allow up to 10 minutes before timing out. Accept-Ranges: bytes means an interrupted download can be resumed rather than restarted.

Editing a config in our admin panel changes build_hash, which invalidates the cached archive — you can never be served a build that embeds a config we have since changed.

Request
cURL
# -OJ writes the file under the name the server sends
curl -OJ "https://api.counter-strike-boost.com/v1/server-builder/download?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
JavaScript
const res = await fetch(
  "https://api.counter-strike-boost.com/v1/server-builder/download?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1"
);

if (!res.ok) {
  const err = await res.json();   // { status: false, error: "..." }
  throw new Error(err.error);
}

await writeFile("cs16-server.zip", Buffer.from(await res.arrayBuffer()));
PHP
<?php
$ch = curl_init("https://api.counter-strike-boost.com/v1/server-builder/download?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code !== 200) {
    $err = json_decode($body, true);
    throw new RuntimeException($err['error'] ?? 'request_failed');
}

file_put_contents("cs16-server.zip", $body);
Python
import requests

r = requests.get(
    "https://api.counter-strike-boost.com/v1/server-builder/download?components=rehlds,regamedll,metamod-r,amxmodx&optimized=1",
    timeout=600,          # a cold server build can take minutes
)
r.raise_for_status()

with open("cs16-server.zip", "wb") as f:
    f.write(r.content)
Response

The response body is the ZIP. Real headers from a build:

content-typeapplication/zip
content-dispositionattachment; filename="cs16-server-0aeb1ead3157.zip"
content-length1340093
cache-controlpublic, max-age=3600
x-csb-build-hash0aeb1ead3157492ff13ec5b65f66b79268af5fdbc416f569ffb881de2d4343d8
x-content-type-optionsnosniff
accept-rangesbytes

Selection errors #

An impossible stack returns 422 invalid_selection with a machine-readable body, so your UI can point at the exact component that is wrong instead of just failing. Up to five arrays can be present; only the non-empty ones are included, and docs always is.

FieldMeaning
unknownstring[] — no such component slug. The caller's typo. Never retry.
unavailable{component, name, reason}[] — a real component we have not mirrored yet, or a pinned version that is not mirrored. Our catalog, not your bug — worth retrying later.
conflicts{component, with, reason}[] — two components that cannot coexist. reason is written for end users; show it verbatim.
missing_requires{component, requires, reason}[] — a dependency was not selected. Offer to add requires and rebuild.
layer_conflicts{layer, components}[] — two components in the same exclusive layer. components is a comma-separated string, not an array.

The distinction between unknown and unavailable is deliberate: one is a typo, the other is our mirror catching up, and only the second is worth trying again tomorrow.

422 Unknown slug
GET /v1/server-builder/resolve?components=quake
Response
{
  "docs": "https://counter-strike-boost.com/developers",
  "error": "invalid_selection",
  "status": false,
  "unknown": ["quake"]
}
422 Not mirrored yet
GET /v1/server-builder/resolve?components=rehlds,dproto
Response
{
  "docs": "https://counter-strike-boost.com/developers",
  "error": "invalid_selection",
  "status": false,
  "unavailable": [
    {
      "component": "dproto",
      "name": "dProto",
      "reason": "no mirrored version available yet"
    }
  ]
}
422 Two components, one exclusive layer
GET /v1/server-builder/resolve?components=rehlds,metamod-r,amxmodx,amxmodx-19
Response
{
  "conflicts": [
    {
      "component": "amxmodx",
      "reason": "Pick one AMX Mod X line.",
      "with": "amxmodx-19"
    }
  ],
  "docs": "https://counter-strike-boost.com/developers",
  "error": "invalid_selection",
  "layer_conflicts": [
    {
      "components": "amxmodx, amxmodx-19",
      "layer": "amxmodx"
    }
  ],
  "status": false
}
422 Missing dependencies
GET /v1/server-builder/resolve?components=reapi,metamod-r
Response
{
  "docs": "https://counter-strike-boost.com/developers",
  "error": "invalid_selection",
  "missing_requires": [
    { "component": "reapi", "requires": "rehlds",    "reason": "ReAPI exposes the ReHLDS API — it will not load on a stock engine." },
    { "component": "reapi", "requires": "regamedll", "reason": "ReAPI exposes the ReGameDLL API." },
    { "component": "reapi", "requires": "amxmodx",   "reason": "ReAPI is an AMX Mod X module." }
  ],
  "status": false
}

Recipe: build a server from a panel #

Validate, show the user the cost, then download. Never skip the resolve step — it is free, it is the same validation, and it turns a failed 100 MB download into an actionable message.

Implementation
Shell
API=https://api.counter-strike-boost.com
STACK="rehlds,regamedll,metamod-r,amxmodx,reapi,reunion"

# 1. what can the user pick?
curl -s "$API/v1/server-builder/meta" | jq '.components[] | {slug, layer, is_default}'

# 2. dry run — validates, and prints the size before anything is transferred
curl -s "$API/v1/server-builder/resolve?components=$STACK&optimized=1" \
  | jq '{build_hash, file_count, total_bytes, notes}'

# 3. build it (omit ?components= entirely for our recommended stack)
curl -OJ "$API/v1/server-builder/download?components=$STACK&optimized=1"

# 4. unzip over the folder CONTAINING cstrike/ — not inside it
unzip -o cs16-server-*.zip -d /opt/hlds/
Node.js
const API = 'https://api.counter-strike-boost.com';
const qs  = new URLSearchParams({
  components: 'rehlds,regamedll,metamod-r,amxmodx,reapi,reunion',
  optimized: '1',
});

// 1. Dry run first — same validation as the download, zero bytes transferred.
const res  = await fetch(`${API}/v1/server-builder/resolve?${qs}`);
const body = await res.json();

if (!res.ok) {
  // 422 invalid_selection: point at the exact component that is wrong.
  for (const c of body.conflicts        ?? []) ui.error(`${c.component} ✕ ${c.with}: ${c.reason}`);
  for (const m of body.missing_requires ?? []) ui.error(`${m.component} needs ${m.requires}: ${m.reason}`);
  for (const l of body.layer_conflicts  ?? []) ui.error(`only one ${l.layer}: ${l.components}`);
  for (const u of body.unknown          ?? []) ui.error(`no such component: ${u}`);
  for (const u of body.unavailable      ?? []) ui.warn (`${u.name}: ${u.reason}`);
  return;
}

// Non-fatal remarks live here — a typo'd plugin slug shows up as a note.
(body.notes ?? []).forEach(n => ui.warn(n));
ui.confirm(`${body.file_count} files, ${(body.total_bytes / 1e6).toFixed(1)} MB`);

// 2. Same selection -> same build_hash -> cache hit. Cold builds take a while.
const zip = await fetch(body.download_url, { signal: AbortSignal.timeout(600_000) });
if (zip.headers.get('x-csb-build-hash') !== body.build_hash) {
  throw new Error('build changed between preview and download');
}
await writeFile('cs16-server.zip', Buffer.from(await zip.arrayBuffer()));

Cache by build_hash. If the hash you get from resolve matches one you already downloaded, you can skip the download entirely — the archive is byte-identical.

Something wrong or missing?

These pages are generated from the live API contract and verified against production on 2026-07-31. If a response here does not match what you got, tell us — that is a bug on our side.