API reference

The TurboStack REST API, endpoint by endpoint, each with request samples in cURL, PHP and Python, plus the Swagger explorer and the configuration model.

The TurboStack API lets you read and manage Clients, Groups and Hosts programmatically - the same objects you manage in the TurboStack Platform. Use it to automate configuration changes and deployments from your own tooling.

  • Base URL (Uniform Resource Locator): https://my.turbostack.app
  • Version: v1 - every path is under /api/v1
  • Format: JSON request and response bodies
  • Auth: an HTTP Bearer token on every request

Interactive API explorer (Swagger)

The platform ships an interactive Swagger UI, a user interface (UI) where you can browse every endpoint, see its parameters and response schema, and try calls live. Open it at /swagger; the full machine-readable spec is at /api-docs/api-docs.json.

The TurboStack API in Swagger UI, grouped into Clients, Hosts and Groups
The TurboStack API in Swagger UI, grouped into Clients, Hosts and Groups

Expand an endpoint to see its parameters with their defaults and its response codes and example payloads; Try it out sends a live request from the browser.

The GET /api/v1/hosts endpoint expanded in Swagger UI, showing its query parameters and its 200 response
The GET /api/v1/hosts endpoint expanded in Swagger UI, showing its query parameters and its 200 response

Authentication

Every endpoint requires an Authorization: Bearer <token> header:

Authorization: Bearer <token>

Create a token under API tokens in your Profile Settings. In Swagger UI, click Authorize and enter Bearer <token>.

The Swagger Authorize dialog for the bearer token
The Swagger Authorize dialog for the bearer token

Rate limit

The API accepts 6000 requests per minute, counted per token owner (or per IP address for an unauthenticated request). That is generous enough that normal integrations never reach it.

Go over it and the API answers 429 Too Many Requests with a Retry-After header telling you how many seconds to wait. The response also carries X-RateLimit-Limit and X-RateLimit-Remaining, so you can back off before you get there.

The limit is about protecting the platform, not about pacing your work. If you are close to it, the usual cause is polling in a tight loop. A deployment takes minutes, so check its status every few seconds at most - see Common workflow: update a host and deploy.

Setup for the examples

The PHP and Python samples below assume this one-time setup. The cURL samples read the token from the TURBOSTACK_API_TOKEN environment variable.

export TURBOSTACK_API_TOKEN="<your-token>"
<?php
// Minimal client (cURL, no dependencies). Reused by every PHP sample below.
function ts(string $method, string $path, ?array $body = null): array
{
    $base  = 'https://my.turbostack.app';
    $token = getenv('TURBOSTACK_API_TOKEN');   // never hard-code your token

    $ch = curl_init($base . $path);
    $headers = ["Authorization: Bearer {$token}", "Accept: application/json"];
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    if ($body !== null) {
        $headers[] = "Content-Type: application/json";
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $res    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status >= 400) {
        throw new RuntimeException("TurboStack API error: HTTP {$status} - {$res}");
    }
    return json_decode($res, true) ?? [];
}
import os
import requests

BASE = "https://my.turbostack.app"
session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {os.environ['TURBOSTACK_API_TOKEN']}",  # never hard-code your token
    "Accept": "application/json",
})

The configuration model

Host and group configuration uses the same desired-state model as the TurboStack Platform. Where the Platform shows it as YAML (the Source (YAML) view), the API represents it as JSON. You read the current state with a GET and submit changes by POSTing an updated JSON body. Every key of that model is listed in the YAML configuration reference, including which level it belongs to and what it changes on the server.

Pagination

List endpoints (clients, hosts and groups) return a paginated response. The items are under the data array, alongside pagination metadata:

{
  "data": [ ],
  "current_page": 1,
  "per_page": 15,
  "total": 42,
  "last_page": 3
}

Control paging with the page and size query parameters, and narrow the results with search. To walk every page, request page=1 and keep incrementing page until current_page equals last_page. A page beyond the last one is not an error: it returns 200 with an empty data.


Clients

A client is an account that owns groups and hosts.

GET /api/v1/clients

List all clients you can access. The result is a paginated list (see Pagination below).

Query parameters

Name Type Description
page integer Page number (default 1).
size integer Items per page (default 15).
search string Filter by name, email, company or ID (partial match).
curl "https://my.turbostack.app/api/v1/clients?page=1&size=50&search=acme" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$clients = ts('GET', '/api/v1/clients?page=1&size=50&search=acme');
clients = session.get(
    f"{BASE}/api/v1/clients",
    params={"page": 1, "size": 50, "search": "acme"},
).json()

Response 200

{
  "data": [
    { "id": 7, "firstname": "Ada", "lastname": "Lovelace", "email": "ada@acme.example" },
    { "id": 8, "firstname": "Alan", "lastname": "Turing", "email": "alan@example.test" }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 2,
  "last_page": 1
}

GET /api/v1/clients/{id}

Fetch one client by its id.

Path parameters

Name Type Description
id integer The client ID.
curl "https://my.turbostack.app/api/v1/clients/7" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$client = ts('GET', '/api/v1/clients/7');
client = session.get(f"{BASE}/api/v1/clients/7").json()

Response 200

{ "id": 7, "firstname": "Ada", "lastname": "Lovelace", "email": "ada@acme.example" }

GET /api/v1/clients/{id}/hosts

List the hosts that belong to a client. The result is a paginated list (see Pagination below).

Path parameters

Name Type Description
id integer The client ID.

Query parameters

Name Type Description
page integer Page number (default 1).
size integer Items per page (default 15).
search string Filter by host name (partial match).
curl "https://my.turbostack.app/api/v1/clients/7/hosts?page=1&size=50&search=shop" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$hosts = ts('GET', '/api/v1/clients/7/hosts?page=1&size=50&search=shop');
hosts = session.get(
    f"{BASE}/api/v1/clients/7/hosts",
    params={"page": 1, "size": 50, "search": "shop"},
).json()

Response 200

{
  "data": [
    { "id": 123, "client_id": 7, "active": 1, "name": "shop-production" }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 1,
  "last_page": 1
}

GET /api/v1/clients/{id}/groups

List the groups that belong to a client. The result is a paginated list (see Pagination below).

Path parameters

Name Type Description
id integer The client ID.

Query parameters

Name Type Description
page integer Page number (default 1).
size integer Items per page (default 15).
search string Filter by group name (partial match).
curl "https://my.turbostack.app/api/v1/clients/7/groups?page=1&size=50&search=security" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$groups = ts('GET', '/api/v1/clients/7/groups?page=1&size=50&search=security');
groups = session.get(
    f"{BASE}/api/v1/clients/7/groups",
    params={"page": 1, "size": 50, "search": "security"},
).json()

Response 200

{
  "data": [
    { "id": 42, "client_id": 7, "name": "shared-security" }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 1,
  "last_page": 1
}

Hosts

A host is a server you configure and deploy. Its configuration uses the configuration model described above.

GET /api/v1/hosts

List all hosts. The result is a paginated list (see Pagination below), filterable by name with the search parameter.

Query parameters

Name Type Description
page integer Page number (default 1).
size integer Items per page (default 15).
search string Filter by name (partial match).
curl "https://my.turbostack.app/api/v1/hosts?page=1&size=50&search=shop" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$hosts = ts('GET', '/api/v1/hosts?page=1&size=50&search=shop');
hosts = session.get(
    f"{BASE}/api/v1/hosts",
    params={"page": 1, "size": 50, "search": "shop"},
).json()

Response 200

Each host carries its configuration under the json key and a live monitoring object.

{
  "data": [
    {
      "id": 123,
      "client_id": 7,
      "active": 1,
      "name": "shop-production",
      "json": { "webserver": "nginx", "mysql_version": "8.4", "redis_enabled": true },
      "monitoring": { "load": {}, "disk": {}, "memory": {} }
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 1,
  "last_page": 1
}

GET /api/v1/hosts/{id}

Fetch one host's full configuration (the desired-state model).

Path parameters

Name Type Description
id integer The host ID.
curl "https://my.turbostack.app/api/v1/hosts/123" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$host = ts('GET', '/api/v1/hosts/123');
host = session.get(f"{BASE}/api/v1/hosts/123").json()

Response 200

The configuration model is returned under the json key.

{
  "id": 123,
  "client_id": 7,
  "active": 1,
  "name": "shop-production",
  "json": {
    "webserver": "nginx",
    "mysql_version": "8.4",
    "redis_enabled": true
  },
  "monitoring": { "load": {}, "disk": {}, "memory": {} }
}

POST /api/v1/hosts/{id}

Update a host's configuration. The request body wraps the configuration model in a json key (the same model as the Source view). This saves the desired state; it does not deploy on its own.

Path parameters

Name Type Description
id integer The host ID.
curl -X POST "https://my.turbostack.app/api/v1/hosts/123" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "json": { "webserver": "nginx", "mysql_version": "8.4", "redis_enabled": true } }'
$updated = ts('POST', '/api/v1/hosts/123', [
    'json' => [
        'webserver'     => 'nginx',
        'mysql_version' => '8.4',
        'redis_enabled' => true,
    ],
]);
updated = session.post(
    f"{BASE}/api/v1/hosts/123",
    json={"json": {"webserver": "nginx", "mysql_version": "8.4", "redis_enabled": True}},
).json()

Response 200

{ "message": "Host saved successfully" }

POST /api/v1/hosts/{id}/deploy

Start a deployment of the host, applying its saved configuration. The request body must specify the deployment type.

Path parameters

Name Type Description
id integer The host ID.

Request body

Name Type Description
type string Required. One of deploy, fullDeploy or fullDeleteDeploy.
  • deploy - a standard deploy of the current configuration.
  • fullDeploy - a full deploy that re-applies the complete configuration.
  • fullDeleteDeploy - a full deploy that also removes resources no longer in the configuration.
curl -X POST "https://my.turbostack.app/api/v1/hosts/123/deploy" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "type": "deploy" }'
$deploy = ts('POST', '/api/v1/hosts/123/deploy', ['type' => 'deploy']);
deploy = session.post(
    f"{BASE}/api/v1/hosts/123/deploy",
    json={"type": "deploy"},
).json()

Response 200

{ "message": "Deploy started" }

GET /api/v1/hosts/{id}/deploy

Get the status of the host's most recent deployment.

Path parameters

Name Type Description
id integer The host ID.
curl "https://my.turbostack.app/api/v1/hosts/123/deploy" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$status = ts('GET', '/api/v1/hosts/123/deploy');
status = session.get(f"{BASE}/api/v1/hosts/123/deploy").json()

Response 200

{
  "publishing": true,
  "publishing_status": "deploy",
  "is_deploying": true,
  "job_stdout": "",
  "deploy_at": "2026-07-22T09:30:00Z",
  "deploy_type": "deploy"
}

Response fields

Field Type Description
publishing boolean Whether this deployment is still marked as active.
publishing_status string Where the deployment stands. See the values below.
is_deploying boolean Whether the host is deploying right now. Poll this if you only need to know "busy or not".
job_stdout string Output of the deployment job, when there is any.
deploy_at string When the deployment finished, as a timestamp like 2026-07-22T09:30:00Z.
deploy_type string The deployment that was run: deploy, Full Deploy or Full Delete Deploy.

Values of publishing_status

Value Meaning Finished?
deploy The deployment is running. No
import An account import is running on this host, not a configuration deployment. No
published The deployment finished successfully. Yes
error The deployment failed. job_stdout usually says where. Yes
canceled The deployment was cancelled before it finished. Yes
timeout The deployment stopped reporting progress and was released. See below. Yes

A deployment that stops reporting progress for more than five minutes is set to timeout automatically, and publishing goes back to false so a new deployment can start. That is a safeguard against a host being locked by a deployment that never reports back - it does not necessarily mean the work on the server failed. Check the host's Publishing history before you retry.

If the host has no deploy history, the endpoint returns 404 with { "error": "No deploy history found" }.

DELETE /api/v1/hosts/{id}/deploy

Reset a stuck deploy state so a new deployment can be triggered.

Path parameters

Name Type Description
id integer The host ID.
curl -X DELETE "https://my.turbostack.app/api/v1/hosts/123/deploy" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$reset = ts('DELETE', '/api/v1/hosts/123/deploy');
reset = session.delete(f"{BASE}/api/v1/hosts/123/deploy").json()

Response 200

{ "message": "Deploy reset" }

GET /api/v1/hosts/{id}/credentials

Get the host's connection credentials. Sensitive - handle the response securely.

Path parameters

Name Type Description
id integer The host ID.
curl "https://my.turbostack.app/api/v1/hosts/123/credentials" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$credentials = ts('GET', '/api/v1/hosts/123/credentials');
credentials = session.get(f"{BASE}/api/v1/hosts/123/credentials").json()

Response 200

The master_user and admin_user blocks are only returned for admin-access or cms-access tokens.

{
  "hostname": "shop-production.example.com",
  "master_user": { "user": "prod", "password": "..." },
  "admin_user": { "user": "admin", "password": "..." },
  "facts": {},
  "network": {},
  "platform": {},
  "system_users": [],
  "ftp_users": []
}

Groups

A group holds shared configuration that its member hosts inherit.

GET /api/v1/groups

List all groups you can access. The result is a paginated list (see Pagination below).

Query parameters

Name Type Description
page integer Page number (default 1).
size integer Items per page (default 15).
search string Filter by name (partial match).
curl "https://my.turbostack.app/api/v1/groups?page=1&size=50&search=security" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$groups = ts('GET', '/api/v1/groups?page=1&size=50&search=security');
groups = session.get(
    f"{BASE}/api/v1/groups",
    params={"page": 1, "size": 50, "search": "security"},
).json()

Response 200

Each group carries its configuration under the json key.

{
  "data": [
    {
      "id": 42,
      "client_id": 7,
      "name": "shared-security",
      "json": { "firewall_whitelist": ["203.0.113.10"] }
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 1,
  "last_page": 1
}

GET /api/v1/groups/{id}

Fetch one group's configuration.

Path parameters

Name Type Description
id integer The group ID.
curl "https://my.turbostack.app/api/v1/groups/42" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Accept: application/json"
$group = ts('GET', '/api/v1/groups/42');
group = session.get(f"{BASE}/api/v1/groups/42").json()

Response 200

The configuration model is returned under the json key.

{
  "id": 42,
  "client_id": 7,
  "name": "shared-security",
  "json": { "firewall_whitelist": ["203.0.113.10"] }
}

POST /api/v1/groups/{id}

Save or update a group's configuration. The request body wraps the configuration model in a json key.

Path parameters

Name Type Description
id integer The group ID.
curl -X POST "https://my.turbostack.app/api/v1/groups/42" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "json": { "firewall_whitelist": ["203.0.113.10", "198.51.100.0/24"] } }'
$saved = ts('POST', '/api/v1/groups/42', [
    'json' => [
        'firewall_whitelist' => ['203.0.113.10', '198.51.100.0/24'],
    ],
]);
saved = session.post(
    f"{BASE}/api/v1/groups/42",
    json={"json": {"firewall_whitelist": ["203.0.113.10", "198.51.100.0/24"]}},
).json()

Response 200

{ "message": "Group saved successfully" }

Common workflow: update a host and deploy

Saving a change and applying it are two steps: POST the new configuration, then trigger a deployment, then poll its status.

# 1. update the host's configuration (saves the desired state)
curl -X POST "https://my.turbostack.app/api/v1/hosts/123" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "json": { "webserver": "nginx", "mysql_version": "8.4" } }'

# 2. deploy the host to apply the change
curl -X POST "https://my.turbostack.app/api/v1/hosts/123/deploy" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "type": "deploy" }'

# 3. check the deployment status
curl "https://my.turbostack.app/api/v1/hosts/123/deploy" \
  -H "Authorization: Bearer $TURBOSTACK_API_TOKEN"
ts('POST', '/api/v1/hosts/123', ['json' => ['webserver' => 'nginx', 'mysql_version' => '8.4']]);
ts('POST', '/api/v1/hosts/123/deploy', ['type' => 'deploy']);
$status = ts('GET', '/api/v1/hosts/123/deploy');
session.post(f"{BASE}/api/v1/hosts/123", json={"json": {"webserver": "nginx", "mysql_version": "8.4"}})
session.post(f"{BASE}/api/v1/hosts/123/deploy", json={"type": "deploy"})
status = session.get(f"{BASE}/api/v1/hosts/123/deploy").json()