Zaroz
Teams and organizations

Organizations, projects, members and invitations

An organization is who pays and who may do what. It holds projects, and projects hold what you buy. This guide shows the day-to-day calls to set one up and to run a team. The samples use the helper from Using the Zaroz API.

Roles

RoleCan
ownerEverything, including deleting the organization and managing other owners
adminBuy, manage projects and members (not above their own role), create organization API tokens
billingPay invoices and read billing
memberUse the organization's services; see its projects, members and invoices

Two rules protect you from locking yourself out: you cannot demote or remove the last owner, and you cannot change a member whose role is above yours (403 member-outranks-caller). Anyone may remove themselves. A personal organization (every account has one) takes no other members: adding one answers 422 member-personal-organization.

Create an organization

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/organizations" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"name": "Acme Games", "slug": "acme-games"}' # {"id": "...", "name": "Acme Games", "slug": "acme-games", "role": "owner", ...}

TypeScript

TypeScriptCode
import { call } from "./zaroz"; const org = await call("POST", "/organizations", { name: "Acme Games", slug: "acme-games" }); const ORG = org.body.id;

Python

Code
from zaroz import call _, org = call("POST", "/organizations", {"name": "Acme Games", "slug": "acme-games"}) ORG = org["id"]

You become its owner. GET /organizations lists every organization you belong to, and GET /organizations/by-slug/{slug} finds one by its slug. PATCH /organizations/{id} renames it.

Projects

A project groups services. The slug is unique inside the organization.

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/organizations/$ORG/projects" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"name": "Production", "slug": "production", "description": "Live servers"}' curl -sS -X PATCH "$ZAROZ_API/projects/$PROJECT" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"description": null}' # null clears it; omitted fields are unchanged curl -sS "$ZAROZ_API/projects/$PROJECT/stats" -H "$AUTH" curl -sS -X DELETE "$ZAROZ_API/projects/$PROJECT" -H "$AUTH" # 204, only when it holds no subscriptions

TypeScript

TypeScriptCode
const project = await call("POST", `/organizations/${ORG}/projects`, { name: "Production", slug: "production", }); await call("PATCH", `/projects/${project.body.id}`, { name: "Live" }); const del = await call("DELETE", `/projects/${project.body.id}`); console.log(del.status); // 204, or 4xx while it still holds subscriptions

Python

Code
_, project = call("POST", f"/organizations/{ORG}/projects", {"name": "Production", "slug": "production"}) call("PATCH", f"/projects/{project['id']}", {"name": "Live"}) status, _ = call("DELETE", f"/projects/{project['id']}") # 204, or 4xx while it still holds subscriptions

In a personal organization the slug games is reserved for the gaming dashboard (422 project-slug-reserved) and that project cannot be changed here (403 project-gaming-managed).

Invite people

Invite by email address with the role they should get. The answer is the same whether or not the address already has an account, so the API cannot be used to find out who is registered.

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/organizations/$ORG/invitations" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"email": "[email protected]", "role": "member"}' curl -sS "$ZAROZ_API/organizations/$ORG/invitations" -H "$AUTH" # pending, unexpired curl -sS -X DELETE "$ZAROZ_API/organizations/$ORG/invitations/$INVITATION" -H "$AUTH" # withdraw one

TypeScript

TypeScriptCode
const inv = await call("POST", `/organizations/${ORG}/invitations`, { email: "[email protected]", role: "member", }); console.log(inv.body.status, inv.body.expires_at); // "pending", ... const pending = await call("GET", `/organizations/${ORG}/invitations`);

Python

Code
_, inv = call("POST", f"/organizations/{ORG}/invitations", {"email": "[email protected]", "role": "member"}) print(inv["status"], inv["expires_at"]) # "pending", ... _, pending = call("GET", f"/organizations/{ORG}/invitations")

Your own email must be verified before you can invite. Sending is rate-limited, and an address that declined has a 30-day cool-down before it can be invited again; both answer 429 with a machine-readable code.

The invited person, signed in with the same verified email, sees the invitation and answers it:

curl

TerminalCode
curl -sS "$ZAROZ_API/me/invitations?status=pending" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/invitations/$INVITATION/accept" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/invitations/$INVITATION/decline" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/invitations/$INVITATION/allow-reinvite" -H "$AUTH" # lift the cool-down early

TypeScript

TypeScriptCode
const mine = await call("GET", "/me/invitations?status=pending"); await call("POST", `/invitations/${mine.body[0].id}/accept`);

Python

Code
_, mine = call("GET", "/me/invitations?status=pending") call("POST", f"/invitations/{mine[0]['id']}/accept")

Accepting is repeatable (a second accept changes nothing) and also works on a declined, unexpired invitation. If you already belong to that organization the answer is 409 and the invitation is cancelled: accepting never changes an existing member's role.

Add, change and remove members

If you already know a person's account id you can add them directly, without an invitation.

curl

TerminalCode
curl -sS "$ZAROZ_API/organizations/$ORG/members" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/organizations/$ORG/members" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"user_id": "<user uuid>", "role": "member"}' curl -sS -X PATCH "$ZAROZ_API/organizations/$ORG/members/$USER" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"role": "admin"}' curl -sS -X DELETE "$ZAROZ_API/organizations/$ORG/members/$USER" -H "$AUTH" # 204

TypeScript

TypeScriptCode
const members = await call("GET", `/organizations/${ORG}/members`); for (const m of members.body) console.log(m.name, m.email, m.role); await call("PATCH", `/organizations/${ORG}/members/${USER}`, { role: "admin" }); await call("DELETE", `/organizations/${ORG}/members/${USER}`);

Python

Code
_, members = call("GET", f"/organizations/{ORG}/members") for m in members: print(m["name"], m["email"], m["role"]) call("PATCH", f"/organizations/{ORG}/members/{USER}", {"role": "admin"}) call("DELETE", f"/organizations/{ORG}/members/{USER}")

Re-adding an existing member returns the membership unchanged. Reading the member list of an organization you do not belong to answers 404, the same as one that does not exist.

Billing country

A purchase in a shared organization needs the country it is taxed under. A quote answers billing-country-required until it is set. Owners and admins set it once; repeating the same value succeeds and a different one is refused with billing-country-already-set.

curl

TerminalCode
curl -sS -X PUT "$ZAROZ_API/organizations/$ORG/billing-country" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"country_code": "ES"}'

TypeScript

TypeScriptCode
await call("PUT", `/organizations/${ORG}/billing-country`, { country_code: "ES" });

Python

Code
call("PUT", f"/organizations/{ORG}/billing-country", {"country_code": "ES"})

Verify a domain

Proving that the organization controls a domain is done with a DNS TXT record. Start the challenge, publish the record, then ask for a check (the platform also rechecks on its own).

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/organizations/$ORG/domains" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"domain": "example.com"}' # {"domain":"example.com","record_type":"TXT","record_name":"...","record_value":"...","status":"...", ...} # publish the TXT record at record_name with record_value, then: curl -sS -X POST "$ZAROZ_API/organizations/$ORG/domains/example.com/check" -H "$AUTH" curl -sS "$ZAROZ_API/organizations/$ORG/domains" -H "$AUTH" curl -sS -X DELETE "$ZAROZ_API/organizations/$ORG/domains/example.com" -H "$AUTH"

TypeScript

TypeScriptCode
const challenge = await call("POST", `/organizations/${ORG}/domains`, { domain: "example.com" }); console.log(`TXT ${challenge.body.record_name} = ${challenge.body.record_value}`); // after publishing the record: const checked = await call("POST", `/organizations/${ORG}/domains/example.com/check`); console.log(checked.body.status, checked.body.last_error);

Python

Code
_, challenge = call("POST", f"/organizations/{ORG}/domains", {"domain": "example.com"}) print("TXT", challenge["record_name"], "=", challenge["record_value"]) # after publishing the record: _, checked = call("POST", f"/organizations/{ORG}/domains/example.com/check") print(checked["status"], checked["last_error"])

The check performs a real DNS lookup and is tightly rate-limited, so wait for the record to propagate before calling it again. last_error says what the lookup found. (This proves ownership of a domain for the organization; to put a domain in front of a service see Custom domains.)

Activity log

Everything done to the organization is listed, newest first, with who did it (a person, or an API token by name). Narrow it with project_id, service_instance_id or action.

curl

TerminalCode
curl -sS "$ZAROZ_API/organizations/$ORG/audit?page=1&page_size=50" -H "$AUTH" curl -sS "$ZAROZ_API/service-instances/$SERVICE/activity" -H "$AUTH" # one service only

TypeScript

TypeScriptCode
const log = await call("GET", `/organizations/${ORG}/audit?page_size=20`); for (const e of log.body.data) console.log(e.created_at, e.action, e.principal_name ?? e.api_token_name);

Python

Code
_, log = call("GET", f"/organizations/{ORG}/audit?page_size=20") for e in log["data"]: print(e["created_at"], e["action"], e["principal_name"] or e["api_token_name"])

Platform-internal events never appear here: only what customers did to your organization.

Delete an organization

Only an owner can, and only when it is empty: no projects (delete them first) and no wallet balance. A personal organization cannot be deleted.

TerminalCode
curl -sS -X DELETE "$ZAROZ_API/organizations/$ORG" -H "$AUTH" # 204
Last modified on