Zaroz
Using the API

Using the Zaroz API

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

TerminalCode
export ZAROZ_API="https://kernel.zaroz.cloud/api/v2" export ZAROZ_TOKEN="zaroz_..." # see "Tokens" below AUTH="Authorization: Bearer $ZAROZ_TOKEN"

TypeScript

TypeScriptCode
// 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 }; }

Python

Code
# zaroz.py (pip install requests) import os import requests API = os.environ.get("ZAROZ_API", "https://kernel.zaroz.cloud/api/v2") _session = requests.Session() _session.headers["Authorization"] = "Bearer " + os.environ["ZAROZ_TOKEN"] def call(method, path, body=None, **kwargs): """Returns (status, parsed JSON or None).""" r = _session.request(method, API + path, json=body, timeout=40, **kwargs) return r.status_code, (r.json() if r.content else None)

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:

JSONCode
{ "data": [ { "id": "..." } ], "pagination": { "page": 1, "page_size": 25, "total_items": 61, "total_pages": 3, "has_next_page": true, "has_previous_page": false } }

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.

curl

TerminalCode
curl -sS "$ZAROZ_API/subscriptions?page=1&page_size=50" -H "$AUTH"

TypeScript

TypeScriptCode
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 call page, 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 += 1 print(len(subscriptions))

Errors

Every refusal has an HTTP status and a JSON body with a stable, kebab-case code:

JSONCode
{ "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

TypeScriptCode
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:

curl

TerminalCode
curl -sS "$ZAROZ_API/public/capabilities"
JSONCode
{ "container_registry": true, "object_storage_buckets": true, "domain_connect": true, "unit_log_store": true, "unit_metrics_store": true, "paypal": true, "mollie": true, "nowpayments": true, "mail": true, "web_push": true, "warden": true }

TypeScript

TypeScriptCode
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 requests caps = 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

TerminalCode
curl -sS "$ZAROZ_API/public/incidents" # [{"id","title","message","started_at"}], only incidents happening now curl -sS "$ZAROZ_API/public/maintenances" # [{"id","title","scheduled_at","expected_duration_minutes"}], soonest first

TypeScript

TypeScriptCode
const incidents = await (await fetch(`${API}/public/incidents`)).json(); if (incidents.length) console.log("Platform incident:", incidents[0].title);

Python

Code
incidents = requests.get(API + "/public/incidents", timeout=10).json() if incidents: print("Platform incident:", incidents[0]["title"])

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.

See Sign in, sessions and account security for how to get a session, and Notifications, live streams and support tickets for being told when something happens.

Last modified on