A service principal is a Workload's own identity. It exists so that a hub's code can create duel servers without anyone pasting a personal key into an environment variable.
At most one principal per Workload, created by a person with manage access.
It holds grants: a target Workload of the same project (itself included) and one capability each:
units.read reads units and the event stream.
units.control also creates, starts, stops, destroys and allocates, and includes reading.
It can never change the scaling policy, the webhooks, or its own grants.
Create it and grant access
curl
Code
P="$ZAROZ_API/projects/$PROJECT/service-instances/$SERVICE/principal"curl -sS -X PUT "$P" -H "$AUTH" # 201 created / 200 already therecurl -sS -X PUT "$P/grants" -H "$AUTH" -H "Content-Type: application/json" -d '{ "grants": [ {"target_service_instance_id": "<duels service uuid>", "capability": "units.control"}, {"target_service_instance_id": "<lobbies service uuid>", "capability": "units.read"} ]}'
PUT .../principal is idempotent and a new principal holds no grant. PUT .../principal/grants replaces the whole list (at most 64; an empty list removes everything). You must be able to manage every Workload you name: otherwise grant_target_unavailable and nothing changes. A grant takes effect on the program's next request.
Enabling a principal changes the Workload's pod template, so it applies as units restart, by the Workload's rollout rule.
Code inside a unit
Every unit of a Workload with a principal gets two things: ZAROZ_API_URL in its environment and a token file at /zaroz/credentials/token. The token lives 60 minutes and the platform replaces it before it expires, so read the file on every request (or when you get a 401) and never cache it.
curl
Code
# inside a unit of a service that has a principalcurl -sS "$ZAROZ_API_URL/projects/$ZAROZ_PROJECT_ID/service-instances/$TARGET/units?label=mode:2v2" \ -H "Authorization: Bearer $(cat /zaroz/credentials/token)"
TypeScript
Code
import { readFileSync } from "node:fs";// The mounted token is rotated by the platform: read it per request, never cache it.const token = () => readFileSync("/zaroz/credentials/token", "utf8").trim();const res = await fetch( `${process.env.ZAROZ_API_URL}/projects/${process.env.ZAROZ_PROJECT_ID}/service-instances/${TARGET}/units?label=mode:2v2`, { headers: { Authorization: `Bearer ${token()}` } },);
Python
Code
import osdef token() -> str: # rotated by the platform: read it per request, never cache it with open("/zaroz/credentials/token") as f: return f.read().strip()r = requests.get( f"{os.environ['ZAROZ_API_URL']}/projects/{os.environ['ZAROZ_PROJECT_ID']}/service-instances/{TARGET}/units", params={"label": "mode:2v2"}, headers={"Authorization": "Bearer " + token()}, timeout=10,)
A program outside Zaroz
A matchmaker running on your own server, or a CI job, mints an external credential for the principal. It acts with the principal's grants, has a name, an expiry (1 to 365 days, default 90), is revocable, and tracks last_used_at.
curl
Code
curl -sS -X POST "$P/credentials" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"name": "matchmaker-eu", "expires_in_days": 90}'# {"credential": {...}, "token": "zaroz_sp_..."} the token is shown this once
TypeScript
Code
const cred = await call("POST", `${P}/credentials`, { name: "matchmaker-eu", expires_in_days: 90 });console.log(cred.body.token); // zaroz_sp_...: store it in your secret manager now
Python
Code
status, cred = call("POST", f"{P}/credentials", {"name": "matchmaker-eu", "expires_in_days": 90})print(cred["token"]) # zaroz_sp_...: store it in your secret manager now
Send the token as Authorization: Bearer zaroz_sp_.... List credentials with GET .../principal/credentials and revoke one with DELETE .../principal/credentials/{credential_id}.
Limits and audit
A principal is limited to 50 requests per second. Every call it makes is audited with the principal id and token kind. Disabling the principal (DELETE .../principal) revokes every token. Tearing down the Workload that owns it revokes it too, and tearing down a target Workload deletes the grants that name it.