HTTP API · 1.0
The control-plane API
Everything the Atlas dashboard does, it does through this API. Create groups, publish templates, start and stop servers, read player analytics, manage who on your team can do what — over ordinary JSON on HTTPS.
# Every server in a network, with live player counts.
curl "https://dashboard.atlasgg.com/v1/networks/$NETWORK/servers" \
-H "authorization: Bearer $ATLAS_TOKEN"
Get started
Base URL and conventions
- Base URL
https://dashboard.atlasgg.com/v1- Content type
application/json- Version
- In the path (
/v1), never a header - Timestamps
- Unix milliseconds, or RFC 3339 where named
*_at
The API is served from the dashboard's own hostname, which is why the base URL reads the way it does. Everything documented here lives under /v1.
Every endpoint on this page is scoped to one network and takes its id as the first path segment after /networks/. Network ids are UUIDs; list the ones your token reaches with GET /v1/networks.
The plane also serves the routes the dashboard needs to run itself — signing in, registration, billing. Those are internal to the application, are not supported for outside use, and are deliberately absent here.
No endpoint accepts a network id as a payload field. Authorization is resolved from the path against your credential before a handler runs, so there is no request shape in which the two could disagree.
Get started
Authentication
Send an account API key as a bearer token on every request. Tokens look like atlas_<id>_<secret> and carry the whole account — every network it owns.
A key is the credential this API is designed around. It has no expiry to refresh, no cookie jar to keep, and can be revoked on its own without disturbing anyone's dashboard session.
Bearer authentication is fully implemented and enforced, but nothing currently issues a key — no endpoint, no CLI command, no dashboard screen. Ask us for one until that changes.
The dashboard authenticates differently, with a session cookie its sign-in flow sets. Those sign-in routes exist but are internal to the application: they are not part of this API and are not documented here.
BASE="https://dashboard.atlasgg.com/v1"
# One header, no state, on every request.
curl "$BASE/networks" \
-H "authorization: Bearer $ATLAS_TOKEN"
# Then work inside a network by its id.
curl "$BASE/networks/$NETWORK/overview" \
-H "authorization: Bearer $ATLAS_TOKEN"
Keep the token out of your shell history and out of source control. It reaches every network the account owns.
Get started
Errors
Every failure is JSON with exactly one field. There is no error code enum and no nested detail object — the string is written to be shown to a person.
{ "error": "your role does not allow this (templates.edit)" }
internal errorA network you cannot reach answers 404 — confirming it exists would leak that it exists. A network you are inside, for an action your role does not allow, answers 403 and names the permission, because you can act on that.
401 is deliberately uninformative. An unknown key, a revoked key, an expired key and a suspended account are one response. Distinguishing them tells an attacker which of their guesses was closest.
Get started
Permissions
Access is a role, and a role is a set of permissions plus an optional confinement to particular groups and templates. Every network-scoped endpoint on this page names the single permission it requires.
Acting implies seeing: templates.edit grants templates.view, and console.send grants console.view. You never need to list both.
| Permission | Allows |
|---|---|
| Templates | |
templates.view | Read templates, their files and versions |
templates.edit | Change files, publish versions, change assignment |
templates.create | Create a template |
templates.delete | Delete a template and the servers running it |
| Groups | |
groups.view | Read groups and their scaling settings |
groups.edit | Change scaling, placement and template assignment |
groups.create | Create a group |
groups.delete | Delete a group |
| Servers | |
servers.view | Read servers, the overview, and archived logs |
servers.control | Start, stop, restart and delete servers |
| Console | |
console.view | Read the live console stream |
console.send | Send commands to a server |
| Players | |
players.view | Read analytics, player records, and player activity |
players.forget | Erase a player's identity |
| Nodes | |
nodes.view | Read nodes and their capacity |
nodes.manage | Enrol, rename, retire, target groups, force agent updates |
| Backups | |
backups.view | List backups and policies |
backups.create | Take a backup, set a schedule |
backups.restore | Overwrite a live world with an archive |
backups.delete | Delete a backup |
| Settings & people | |
settings.view | Read module and analytics settings |
settings.edit | Change module and analytics settings |
members.manage | Invite, remove, and define roles |
npcs.manage | Switch the NPC module on, target groups, and read the NPCs |
A role either reaches every group and template, or names the ones it reaches. "All" is a stored flag rather than an empty list, so removing the last named group narrows a role to nothing instead of silently widening it to the whole network.
backups.restore is separate from backups.create because the two differ in what they destroy: one costs storage, the other costs the world. members.manage is withheld from both built-in roles for the same reason — an owner should grant the permission that hands out permissions deliberately, not inherit it from a default.
Infrastructure
Networks
Every network the credential can reach. For a session that includes networks another account invited you into; for an API key it is only the account's own.
The dashboard home in one call: server and node counts, players online, group health, and licence state.
Infrastructure
Servers
Servers are created by the controller from a group's scaling rules, not by a POST. You influence how many exist by editing the group; you act on individual ones here.
Every server, with its group, node, state, player count and uptime. A role confined to particular groups sees only those.
Stops a server. Drains by default, so players are moved before it goes down.
| Field | Type | Description |
|---|---|---|
| drain | boolean | Move players off first. Defaults to true; false stops immediately. |
Starts a stopped persistent server. Servers in dynamic groups are destroyed when they stop and cannot be started again — the controller creates a replacement instead.
Stops and starts in one request.
Tears the server down. Whether its data directory is destroyed follows the owning group's persistence — static keeps the world, dynamic discards it — and is never taken from the request.
Infrastructure
Groups
A group is a fleet of interchangeable servers plus the rules for how many should exist. Editing a group is how you scale.
Every group with its scaling settings and current server count.
| Field | Type | Description |
|---|---|---|
| name | string | Required. Lowercase identifier, unique in the network. |
| display_name | string | Shown in the dashboard. Defaults to the name. |
| kind | string | game or proxy. |
| persistence | string | dynamic wipes the working directory on stop; static keeps it. |
| slots_per_server | integer | Required. Player capacity per server. |
| max_memory_mb | integer | Required. Memory ceiling per server. |
| min_servers | integer | Always keep at least this many running. |
| max_servers | integer | Ceiling. Defaults to unlimited. |
| base_port | integer | First port to allocate from. |
| placement_strategy | string | How the controller picks a node. |
| templates | string[] | Template names to assign at creation. |
Partial update — every field is optional and only what you send changes.
| Field | Type | Description |
|---|---|---|
| enabled | boolean | A disabled group scales to zero and stays there. |
| min_servers | integer | Floor. |
| max_servers | integer | Ceiling. |
| min_non_full | integer | Keep this many servers with room on them. |
| scale_up_fill_percent | integer | Fullness that triggers another server. |
| max_idle | integer | Empty servers to keep before scaling down. |
| idle_timeout_seconds | integer | How long empty counts as idle. |
| cooldown_seconds | integer | Minimum gap between scaling actions. |
| startup_timeout_seconds | integer | Give up on a server that never reports ready. |
| max_lifetime_seconds | integer | Recycle servers older than this. |
| slots_per_server | integer | Capacity per server. |
| max_memory_mb | integer | Memory ceiling per server. |
| max_cpu_permille | integer | CPU ceiling, in thousandths of a core. |
| max_per_node | integer | Cap servers of this group on one node. |
| allowed_node_ids | uuid[] | Restrict placement to these nodes. |
| kind | string | game or proxy. |
| persistence | string | dynamic or static. |
| placement | string | Placement mode. |
| placement_strategy | string | Node-selection strategy. |
| base_port | integer | First port to allocate from. |
Deletes the group and tears down its servers.
Replaces the group's template assignment wholesale.
| Field | Type | Description |
|---|---|---|
| templates | string[] | Template names, in the order they apply. |
Infrastructure
Nodes
Nodes are the machines that run servers. They join by presenting an enrolment token to the agent installer; there is no endpoint that creates one directly.
Every node with its capacity, agent version, address and last heartbeat.
Mints a single-use enrolment token and returns the install command for a new machine.
| Field | Type | Description |
|---|---|---|
| name | string | New display name. |
Controls which groups may be placed on this node. Its own path rather than more fields on the PATCH above, because renaming is cosmetic and this moves workloads.
| Field | Type | Description |
|---|---|---|
| restrict_groups | boolean | false accepts any group. |
| allowed_groups | string[] | Group names, when restricted. |
Asks the node's agent to update itself now rather than on its next scheduled check.
Retires the node: drains its servers, then removes it from placement.
Content
Templates
A template is the file set a server starts from. Files are content-addressed: you upload bytes, get a sha256, then publish a version that binds hashes to paths. Publishing affects servers created afterwards; running servers keep the files they started with.
Every template with its files, versions and group assignment.
| Field | Type | Description |
|---|---|---|
| name | string | Required. Unique in the network. |
| runtime_preset | string | Sets the image and server type, e.g. paper-latest. |
| container_image | string | Custom image, when no preset applies. |
| auto_assign | boolean | Attach to every group except those excluded. |
One template: files with sizes and hashes, version history, and which groups it reaches.
| Field | Type | Description |
|---|---|---|
| auto_assign | boolean | Switch between assign-list and exclude-list. |
| assigned_groups | string[] | Groups to attach to, when not auto-assigning. |
| excluded_groups | string[] | Groups to skip, when auto-assigning. |
| runtime_preset | string | Change the preset. |
| container_image | string | Change the custom image. |
Deletes the template and every server running one of its versions.
Files
Uploads one file as a raw body — content-type: application/octet-stream, not multipart. Returns the sha256 to reference when publishing. Any file type is accepted here; the size limit and your storage quota apply.
| Name | Type | Description |
|---|---|---|
| path | string | Destination inside the server directory, e.g. plugins/Essentials.jar. |
Reads a text file as a string. Restricted to text extensions — cfg conf csv env ini json lang list md properties sh toml txt xml yaml yml — and to files of 1 MB or less. Anything else answers 400.
Writes a text file and publishes a new version in the same call. Creates the file when nothing is at that path, so this is also how you add one without uploading. .json content is parsed and rejected if invalid.
| Field | Type | Description |
|---|---|---|
| content | string | The whole file. 1 MB maximum. |
Removes a file from the template and publishes a new version.
Versions
Publishes a version. The files list is the complete manifest, not a delta: anything omitted is gone from the new version.
| Field | Type | Description |
|---|---|---|
| files | object[] | Required. Each { "sha256": "…", "path": "…" }. |
| directories | string[] | Empty directories to create. |
| environment | object | Environment variables for the container. |
| java_args | string[] | Command arguments. |
| container_image | string | Override the image for this version. |
Provisioning writes the manifest's files into the working directory without wiping it, so a static server keeps the data its plugins wrote. Files the manifest names are replaced; files it does not name are left alone — including files a previous version installed and this one dropped.
Content
Backups
Archives of a persistent server's working directory. Backups apply to static groups; a dynamic server has no world worth keeping.
Every backup with its size, server, and creation time, plus the per-group policies.
Takes a backup now. Returns a job; the archive appears in the list when it finishes.
| Field | Type | Description |
|---|---|---|
| enabled | boolean | Whether the schedule runs. |
| interval_minutes | integer | How often to back up. |
| retain_count | integer | How many to keep before the oldest is dropped. |
Replaces the server's working directory with the archive. The current data is kept until the restored server starts cleanly, and put back if it does not.
Deletes the archive and frees the storage it counted against.
People
Players & analytics
Namespaced under analytics/ rather than hung off /players/{uuid}, so a player whose uuid happened to read as a word could never shadow a collection route.
Headline figures: unique players, sessions, countries, retention and playtime distribution.
| Name | Type | Description |
|---|---|---|
| range | string | Window to report over, e.g. 7d. |
| Name | Type | Description |
|---|---|---|
| search | string | Match on username. |
| limit | integer | Page size. |
| offset | integer | Rows to skip. |
One player: sessions, playtime, countries seen, and which servers they visited.
Erases the player's identity while leaving the aggregate counts intact. Its own permission because it is the one player action that cannot be undone.
What is collected and how long it is kept.
Changes collection and retention.
Live
Console & events
Two Server-Sent Event streams. Both are ordinary GETs that stay open and emit data: frames of JSON; use EventSource in a browser, or read the body as a stream anywhere else.
Live console output. Each frame is { "stdout": true, "line": "…", "at": 1754140000000 } — stdout false means stderr, at is Unix milliseconds.
Sends one command. The same path as the stream with a different method, because the input half is a small POST and only the output half is a stream.
| Field | Type | Description |
|---|---|---|
| command | string | Sent as typed, without a leading slash. |
The network activity feed: servers coming up and failing, and players arriving and leaving. Opens with a short backfill, then streams. Every frame carries kind, which says which shape it is.
Player frames additionally require players.view. A role with servers.view alone receives the same stream with the player half absent rather than an error.
// kind: "server"
{ "kind": "server", "event_id": "…", "state": "ready",
"server_name": "lobby-2", "group": "lobby", "node": "dfw001",
"source": "controller_scale", "reason": "", "at": 1754140000000 }
// kind: "player" — needs players.view
{ "kind": "player", "event_id": "…", "action": "left",
"username": "Notch", "country": "SE",
"duration_seconds": 240, "ended_reason": "quit",
"at": 1754140000000 }
One session produces two player frames sharing an event_id. Key on event_id plus action, or the departure looks like a repeat of the arrival.
People
Members & roles
A member is someone from another account granted access to your network. They keep their own login; membership is a cross-account grant rather than a shared password.
Members, pending invitations, and the roles available to assign.
Emails an invitation. If the address has no Atlas account, the link leads to registration and admits them on completion.
| Field | Type | Description |
|---|---|---|
| string | Who to invite. | |
| role_id | uuid | Role they get on accepting. |
Revokes a pending invitation.
Changes someone's role.
| Field | Type | Description |
|---|---|---|
| role_id | uuid | The role to move them to. |
Removes someone from the network.
Leaves a network you were invited to. Separate from removal so that "remove a member" can refuse to act on yourself without also making it impossible to leave.
Roles
| Field | Type | Description |
|---|---|---|
| name | string | Shown wherever the role is assigned. |
| permissions | string[] | Names from the permission table, e.g. console.view. |
| all_groups | boolean | Reach every group. When false, group_ids is the allowlist. |
| all_templates | boolean | Reach every template. When false, template_ids is the allowlist. |
| group_ids | uuid[] | Groups this role reaches. |
| template_ids | uuid[] | Templates this role reaches. |
Replaces the role's definition. Same body as creation.
Deletes a custom role. Answers 409 if anyone still holds it, and refuses on the built-in roles, which can be edited but not removed.
A non-owner defining or assigning a role may only include permissions they have themselves, and nobody may change their own role or remove themselves. Otherwise members.manage would quietly be a route to every other permission.
The recipient accepts through the link in their email, which the dashboard handles. There is no endpoint here for that half of the flow — it belongs to signing in, not to managing a network.
Reference
Modules
Optional features, each a GET to read the configuration and a PUT to replace it.
Whether Atlas writes the proxy's server list, and which groups it covers.
| Field | Type | Description |
|---|---|---|
| target_groups | string[] | Proxy groups to write into. Empty means all. |
| join_behaviour | string | Where players land on connecting. |
| join_group | string | The group they land in, when pinned. |
Reads the in-game command module's configuration.
Replaces it.
Whether console output is archived when a server ends, and for how long.
Replaces it.
| Name | Type | Description |
|---|---|---|
| group | string | Only logs from this group. |
| reason | string | Why the server ended. |
| search | string | Match on server name. |
| sort | string | Ordering. |
| limit | integer | Page size. |
| offset | integer | Rows to skip. |
The full captured output of one ended server.
NPCs
Server-selector NPCs: fake players that stand in a lobby and send people somewhere. An NPC belongs to a group rather than a server — every server in a group is built from the same template and so runs the same world, which is what makes one NPC appear on all of them.
The module's switch, its targeting, the game-kind groups available to target, and every NPC in the network.
Switches the module on or off and chooses which groups render NPCs. Reaches running servers immediately; nothing restarts.
| Field | Type | Description |
|---|---|---|
| enabled | boolean | Off hides every NPC without deleting any, so switching back on restores what you had. |
| target_mode | string | all_groups or selected. |
| target_groups | string[] | Group names, when selected. Names from another network select nothing. |
Switched on, set to selected, and given no groups is refused with 400 — that combination is a setting that silently does nothing.
Every NPC on its own, for callers not drawing the settings page. Each carries its group, world and coordinates, appearance, what each click does, and its routing rules.
The resolved skin texture is deliberately not returned. It is a Mojang blob that only the server plugin needs, and it is the largest thing in the row.
Edits an NPC's appearance, behaviour, click actions and routing. Placement stays in game. The complete editable body contains display_name, skin_owner_name, item_in_hand, look_at_player, imitate_player, info_lines, left_click_action, right_click_action, and routing.
The routing document has its own guide, with worked examples: NPCs & routing rules.
routing.rules are evaluated in order. A rule's when.from_group_in names ingress proxy groups. lowest, highest and random take one group or a groups list and choose between them by live population; last_of returns the player to their most recently visited group among its groups. Every strategy may carry a fallback, nested at most twice. The document-level default is used when no condition matches and may be null.
The destination must be an active game group in this network. A named skin is resolved once and stored; the resolved Mojang texture is never returned by the API.
Deletes one NPC and pushes the new set to running servers immediately.
Creating one means standing where it should go and running /atlasnpc create, because a coordinate typed by somebody who cannot see the world is a coordinate inside a wall. The command needs the Minecraft permission atlas.npc.manage, which is separate from the Atlas role permission above: one governs the person in game, the other the person on this API.
A change made in game is stored centrally and pushed straight back out, so an NPC placed on lobby-1 is standing on every other server in that group a moment later. Nothing restarts.
Reference
Endpoint index
Every network endpoint, with the permission it requires. Paths are relative to https://dashboard.atlasgg.com/v1.
| Method | Path | Requires |
|---|---|---|
| get | /networks | any credential |
| get | /networks/{network_id}/overview | servers.view |
| get | /networks/{network_id}/servers | servers.view |
| post | /networks/{network_id}/servers/{server_id}/start | servers.control |
| post | /networks/{network_id}/servers/{server_id}/stop | servers.control |
| post | /networks/{network_id}/servers/{server_id}/restart | servers.control |
| del | /networks/{network_id}/servers/{server_id} | servers.control |
| get | /networks/{network_id}/groups | groups.view |
| post | /networks/{network_id}/groups | groups.create |
| patch | /networks/{network_id}/groups/{group} | groups.edit |
| del | /networks/{network_id}/groups/{group} | groups.delete |
| put | /networks/{network_id}/groups/{group}/templates | groups.edit |
| get | /networks/{network_id}/nodes | nodes.view |
| post | /networks/{network_id}/nodes/enrollment | nodes.manage |
| patch | /networks/{network_id}/nodes/{node_id} | nodes.manage |
| put | /networks/{network_id}/nodes/{node_id}/groups | nodes.manage |
| post | /networks/{network_id}/nodes/{node_id}/agent-update | nodes.manage |
| del | /networks/{network_id}/nodes/{node_id} | nodes.manage |
| get | /networks/{network_id}/templates | templates.view |
| post | /networks/{network_id}/templates | templates.create |
| get | /networks/{network_id}/templates/{template} | templates.view |
| patch | /networks/{network_id}/templates/{template} | templates.edit |
| del | /networks/{network_id}/templates/{template} | templates.delete |
| post | /networks/{network_id}/templates/{template}/files | templates.edit |
| get | /networks/{network_id}/templates/{template}/file | templates.view |
| put | /networks/{network_id}/templates/{template}/file | templates.edit |
| del | /networks/{network_id}/templates/{template}/file | templates.edit |
| post | /networks/{network_id}/templates/{template}/versions | templates.edit |
| get | /networks/{network_id}/backups | backups.view |
| post | /networks/{network_id}/servers/{server_id}/backups | backups.create |
| put | /networks/{network_id}/backups/policies/{group_id} | backups.create |
| post | /networks/{network_id}/servers/{server_id}/restore/{backup_id} | backups.restore |
| del | /networks/{network_id}/backups/{backup_id} | backups.delete |
| get | /networks/{network_id}/analytics/overview | players.view |
| get | /networks/{network_id}/analytics/series | players.view |
| get | /networks/{network_id}/analytics/players | players.view |
| get | /networks/{network_id}/analytics/players/{player_uuid} | players.view |
| del | /networks/{network_id}/analytics/players/{player_uuid} | players.forget |
| get | /networks/{network_id}/analytics/settings | settings.view |
| put | /networks/{network_id}/analytics/settings | settings.edit |
| get | /networks/{network_id}/servers/{server_id}/console | console.view |
| post | /networks/{network_id}/servers/{server_id}/console | console.send |
| get | /networks/{network_id}/events | servers.view |
| get | /networks/{network_id}/members | members.manage |
| patch | /networks/{network_id}/members/{member_id} | members.manage |
| del | /networks/{network_id}/members/{member_id} | members.manage |
| post | /networks/{network_id}/leave | any credential |
| post | /networks/{network_id}/invites | members.manage |
| del | /networks/{network_id}/invites/{invite_id} | members.manage |
| post | /networks/{network_id}/roles | members.manage |
| patch | /networks/{network_id}/roles/{role_id} | members.manage |
| del | /networks/{network_id}/roles/{role_id} | members.manage |
| get | /networks/{network_id}/modules/proxy-sync | settings.view |
| put | /networks/{network_id}/modules/proxy-sync | settings.edit |
| get | /networks/{network_id}/modules/game-commands | settings.view |
| put | /networks/{network_id}/modules/game-commands | settings.edit |
| get | /networks/{network_id}/modules/server-logs | settings.view |
| put | /networks/{network_id}/modules/server-logs | settings.edit |
| get | /networks/{network_id}/modules/server-logs/archive | servers.view |
| get | /networks/{network_id}/modules/server-logs/archive/{log_id} | servers.view |
| get | /networks/{network_id}/modules/npcs | npcs.manage |
| put | /networks/{network_id}/modules/npcs | npcs.manage |
| get | /networks/{network_id}/npcs | npcs.manage |
| put | /networks/{network_id}/npcs/{npc_id} | npcs.manage |
| del | /networks/{network_id}/npcs/{npc_id} | npcs.manage |
The plane also serves the routes the dashboard needs for itself — signing in, registration, email verification, billing, and Stripe's callback — plus a staff-only diagnostic route that answers 404 for every customer credential. Those are internal to the application and are not part of this API.