Everything the console does, you can do with the API. This page is the part that is the same for every route; the guides that follow (organizations, tokens, billing, domains, databases, backups, services) only describe what is specific to them.
All guides use the same three-line helper. Copy it once.
curl
Code
export ZAROZ_API="https://kernel.zaroz.cloud/api/v2"export ZAROZ_TOKEN="zaroz_..." # see "Tokens" belowAUTH="Authorization: Bearer $ZAROZ_TOKEN"
TypeScript
Code
// zaroz.ts (Node 20+, no dependencies)const API = process.env.ZAROZ_API ?? "https://kernel.zaroz.cloud/api/v2";const TOKEN = process.env.ZAROZ_TOKEN!;export async function call<T = any>( method: string, path: string, body?: unknown, headers: Record<string, string> = {},): Promise<{ status: number; body: T }> { const res = await fetch(`${API}${path}`, { method, headers: { Authorization: `Bearer ${TOKEN}`, ...(body !== undefined ? { "Content-Type": "application/json" } : {}), ...headers, }, body: body !== undefined ? JSON.stringify(body) : undefined, }); const text = await res.text(); return { status: res.status, body: (text ? JSON.parse(text) : null) as T };}
The full, always current list of routes, bodies and responses is the API reference. These guides are the part the reference cannot tell you: which call to make first, and why.
Tokens
Send Authorization: Bearer <token> on every request. A script uses an API token you create in the console or with the API, scoped to exactly the permissions it needs; see API tokens and permissions. A token never has more authority than the person (or organization) that made it.
Pagination
List routes that can grow take page (starting at 1) and page_size, and answer an envelope:
Always follow has_next_page rather than guessing a page count. A list that is not paged (for example the members of an organization) answers a plain JSON array.
import { call } from "./zaroz";let page = 1;const all: any[] = [];for (;;) { const { body } = await call("GET", `/subscriptions?page=${page}&page_size=50`); all.push(...body.data); if (!body.pagination.has_next_page) break; page += 1;}console.log(all.length);
Python
Code
from zaroz import callpage, subscriptions = 1, []while True: _, body = call("GET", f"/subscriptions?page={page}&page_size=50") subscriptions += body["data"] if not body["pagination"]["has_next_page"]: break page += 1print(len(subscriptions))
Errors
Every refusal has an HTTP status and a JSON body with a stable, kebab-case code:
Code
{ "code": "member-outranks-caller", "description": "You cannot change a member whose role is above yours", "details": "member-outranks-caller: You cannot change a member whose role is above yours"}
Switch on code, never on description. The description is a sentence for people and may be reworded or translated; the code does not change.
A 404 is also what you get for something you are not allowed to see, so that the API does not reveal what exists.
429 means slow down. Honor the Retry-After header.
Refusals that a program can act on (the Units API) also carry a docs_url pointing at the section that explains the code and what to do.
A refusal is never a partial success: a request that fails changed nothing.
TypeScript
Code
import { call } from "./zaroz";const r = await call("PATCH", `/organizations/${ORG}/members/${USER}`, { role: "admin" });if (r.status >= 400) { if (r.body.code === "member-outranks-caller") console.log("ask an owner to do this"); else throw new Error(`${r.status} ${r.body.code}`);}
Python
Code
status, body = call("PATCH", f"/organizations/{ORG}/members/{USER}", {"role": "admin"})if status >= 400: if body["code"] == "member-outranks-caller": print("ask an owner to do this") else: raise RuntimeError(f"{status} {body['code']}")
Idempotency
Routes that move money or create something you cannot easily undo (redeeming a wallet code, paying an invoice from the wallet, the Units allocate call) accept an Idempotency-Key header. Repeating the identical request with the same key answers with the first result instead of a refusal or a duplicate, so a client that timed out can simply try again. Use a fresh random value (a UUID) per intended action, and reuse it only when retrying that same action.
What a deployment offers
Zaroz Cloud can be run by partners with a subset of the platform. A deployment may not have a container registry, object storage, a payment provider, Domain Connect or the unit log store configured. Ask it, without authentication:
const caps = await (await fetch(`${API}/public/capabilities`)).json();if (!caps.object_storage_buckets) console.log("this deployment has no object storage");
Python
Code
import requestscaps = requests.get(API + "/public/capabilities", timeout=10).json()if not caps["domain_connect"]: print("show the manual DNS instructions only")
A value of false means the feature is absent on that deployment, not broken:
its routes answer 501 with code capability-not-available;
its products are not in the catalog;
an integration should not offer it at all.
A provider that cannot be reached at this moment also reports false. Cache the answer for a short time (a minute is plenty), not for the life of your program.
Platform status
Two unauthenticated reads tell a program whether the platform itself is having a bad time, even when nobody can sign in. They are what the console's outage and maintenance banners use.
curl
Code
curl -sS "$ZAROZ_API/public/incidents" # [{"id","title","message","started_at"}], only incidents happening nowcurl -sS "$ZAROZ_API/public/maintenances" # [{"id","title","scheduled_at","expected_duration_minutes"}], soonest first
Resolved incidents are never returned, and maintenance windows are those not cancelled and scheduled no earlier than 24 hours ago. Check these before paging someone about a failing deploy.