Control-plane API
REST + JSON for everything the console does — organisations, projects, keys, nodes, usage, webhooks and billing. Media and player tokens live on the cell, not here.
Conventions#
- Base URL
https://aurix.run, all paths under/v1. JSON in and out, UTF-8. - Auth: console sessions and API tokens both use
Authorization: Bearer <token>(ast_…session,aat_…API token). Node agents use their agent token; Paddle webhooks are signature-verified. - Errors:
{ "error": { "code": "…", "message": "…" } }with the HTTP status — see Errors & limits. - Ids are UUIDs; money is integer cents, USD; times are RFC 3339 UTC. Bodies up to 256 KB, 30-second timeout.
curl -H "Authorization: Bearer $AURIX_API_TOKEN" https://aurix.run/v1/orgs/$ORG/projectsAuth & account#
POST/v1/auth/register
{ email, password, display_name, org_name? } → { token, user, org? }. Sends a verification mail.
POST/v1/auth/login
{ email, password } → { token, user } or { mfa_required: true, mfa_token } when two-factor is on; finish with POST /v1/auth/mfa { mfa_token, code }.
POST/v1/auth/logout · /v1/auth/forgot · /v1/auth/reset · /v1/auth/verify · /v1/auth/verify/resend · /v1/auth/email/confirm
Session revocation, password reset ({ email } → always 200; { token, password } → { token }), e-mail verification and e-mail-change confirmation by one-time tokens from the mails.
GET/v1/me PATCH/v1/me DELETE/v1/me
Profile and memberships; rename; delete account ({ password }). Also POST /v1/me/password, POST /v1/me/email, GET /v1/me/export (everything we store, JSON), GET /v1/me/sessions, DELETE /v1/me/sessions[/{id}], GET /v1/me/mfa, POST /v1/me/mfa/setup → { secret, otpauth_url }, POST /v1/me/mfa/enable { code } → { recovery_codes }, DELETE /v1/me/mfa { password, code }.
Catalog#
GET/v1/catalog
Plans, regions, node sizes with prices and participant ratings, cell_api_url default. Public.
Organisations#
POST/v1/orgs GET/v1/orgs/{org} PATCH/v1/orgs/{org} DELETE/v1/orgs/{org}
Create { name }; rename or set billing_email; delete (only when projects are deleted and invoices settled).
GET/v1/orgs/{org}/members PATCH/v1/orgs/{org}/members/{user} DELETE/v1/orgs/{org}/members/{user}
Roles owner | admin | developer | billing | viewer. The last owner cannot be demoted or removed.
GET/v1/orgs/{org}/invites POST/v1/orgs/{org}/invites DELETE/v1/orgs/{org}/invites/{id} POST/v1/invites/accept
Invite { email, role } → { link } (7 days, bound to the address); accept { token } as the signed-in user.
GET/v1/orgs/{org}/tokens POST/v1/orgs/{org}/tokens DELETE/v1/orgs/{org}/tokens/{id}
API tokens { name, role, expires_days? } → { secret } shown once.
GET/v1/orgs/{org}/audit?limit=&before=
Audit entries { created_at, actor, action, target, details }, newest first, 12 months.
Projects#
GET/v1/orgs/{org}/projects POST/v1/orgs/{org}/projects
Create { name, plan, home_region, spend_cap_cents? } → { project, sdk_key } — the key is returned only here and on rotate.
GET/v1/projects/{id} DELETE/v1/projects/{id}
Project with plan_id, home_region, state, ladder_step, spend_cap_cents, autoscale, cell_api_url, sdk_key_prefix.
POST/v1/projects/{id}/keys/rotate
→ { sdk_key }. Old key stops working immediately.
PATCH/v1/projects/{id}/limit PATCH/v1/projects/{id}/autoscale
{ spend_cap_cents?, bitrate?, no_mix?, home_only?, refuse? } (ladder toggles); { autoscale, max_nodes }.
GET/v1/projects/{id}/usage?from=&to=
{ hours: [{ hour, metric, size_id, quantity, cost_cents }], breakdown: [{ metric, quantity, cost_cents }], period: { spent_cents, forecast_cents, hourly_rate_cents } }. Defaults to the current month.
GET/v1/projects/{id}/live?range=24h|7d|30d
What the console's Live page shows, aggregated over every cell of the project and cached 5 s: { now: { participants, channels }, peak_participants, joins_refused, quality: { mos_avg, rtt_avg_ms, loss_avg_percent, poor_percent, samples } | null, capacity, series: [{ bucket, participants, joins_refused, poor_percent }], regions: [{ region, healthy, participants, rtt_avg_ms, mos_avg }], pulse: { has_sdk_key, last_session_at, webhooks, webhooks_failing, regions }, spend: { period_cents, cap_cents, ladder_step }, cells_reachable }. capacity.kind is plan (Free/Indie: limit = concurrent participants the plan admits), nodes (Cloud: limit = capacity of running nodes, nodes_running, autoscale, autoscale_max_nodes) or none (your own servers: nodes_online, no limit). joins_refused counts connects the cap turned away in the range (cells on Aurix ≥ 1.8.3).
GET/v1/projects/{id}/analytics/sessions?from=&to=&limit=&min_samples= GET/v1/projects/{id}/analytics/channels?from=&to=
The cell's worst-quality sessions and busiest channels for the range, concatenated across the project's cells (each row carries regions).
GET/v1/projects/{id}/analytics/regions?from=&to=
How well region recommendations matched what the SDKs actually chose (default range: last 7 days). SDKs post their choice after probing (recommended vs chosen node, RTT per probe, how the server located the player); the response sums selections, with_recommendation, matched_recommendation and match_rate across the project's cells and lists regions rows per chosen region and location_source (coordinates / geoip / none) with chosen_rtt_p50_ms / chosen_rtt_p90_ms and the cell's cell_regions. A low match_rate for geoip means the GeoIP fallback points players at the wrong region; none rows mean your token server sent neither a region nor coordinates.
Nodes#
GET/v1/projects/{id}/nodes POST/v1/projects/{id}/nodes POST/v1/projects/{id}/nodes/byoc POST/v1/nodes/{node}/drain
Add a cloud node { size, region } (202, provisioning); get a BYOC install command { region } → { install_command, expires_at }; drain.
Regions & routing#
GET/v1/projects/{id}/regions POST/v1/projects/{id}/regions DELETE/v1/projects/{id}/regions/{region}
The regions a project serves players in. { region } adds one (Free includes 1 region, Indie 2, Cloud / BYOC unlimited); the home region cannot be removed. Regions on the same backbone share channels — a positional channel can have players connected in Falkenstein and Helsinki at once, audio crosses over the backbone. Regions on a separate backbone (another continent, or an isolated deployment for data residency) do not share channels: use them for regional shards. The response carries sdk_key only when a new backend app had to be created; the control plane uses it for routing, your token server does not need it.
GET/v1/projects/{id}/discover?region=&latitude=&longitude=&client_ip=
Ranks the project's regions for a player — explicit region, else nearest to latitude/longitude, else the request's country (CF-IPCountry, X-Country or CloudFront-Viewer-Country), else the home region — and asks the best healthy cell for its nearest node (pass the player's public client_ip and the cell resolves it through GeoIP when no coordinates were given; the answer says so in location_source): { basis, regions: [{ region, name, cell, cell_healthy, distance_km, primary, nodes }], recommended, endpoint: { ws_url, probe_url, region, nodes, load_factor } }. Clients that can measure RTT should probe probe_url and connect to the lowest.
POST/v1/projects/{id}/tokens
Mints a player token on the cell chosen as above: { external_id, display_name, channels, metadata?, region? | latitude? + longitude? | client_ip? } → { token, user_id, expires_at, channels, endpoint, location_source, region, cell, basis }. Same channels grammar as the cell's /v1/tokens (Player tokens); works with an organisation API token, so a token server needs exactly one secret and no cell addresses. Refusals are forwarded: a cell at capacity is 403 plan_limit, a bad grant is 400 invalid.
Webhooks#
GET/v1/projects/{id}/webhooks POST/v1/projects/{id}/webhooks DELETE/v1/projects/{id}/webhooks/{hook} POST/v1/projects/{id}/webhooks/{hook}/test
{ url, events } → { webhook, secret }; list includes the available events. Webhooks
Moderation & safety#
GET/v1/projects/{id}/moderation/events?status= POST/v1/projects/{id}/moderation/events/{event_id}/resolve
GET/v1/projects/{id}/moderation/bans POST/v1/projects/{id}/moderation/bans POST/v1/projects/{id}/moderation/bans/{ban_id}/revoke
POST/v1/projects/{id}/moderation/mute | mute-all | kick | kick-all | priority | report
GET/v1/projects/{id}/safety/incidents GET/v1/projects/{id}/safety/incidents/{incident_id}/export GET/v1/projects/{id}/safety/users/{user_id}/risk
GET/v1/projects/{id}/players/{user_id}/export DELETE/v1/projects/{id}/players/{user_id}?purge_moderation=
GET/v1/projects/{id}/recordings GET/v1/projects/{id}/recordings/{recording_id} POST…/stop DELETE/v1/projects/{id}/recordings/{recording_id}
Forwarded to the cell hosting the project as its administrator; writes need the developer role or higher and are audited. Payloads and semantics: Moderation & safety.
Billing#
GET/v1/orgs/{org}/billing
{ projects: [{ project, period }], subscriptions, invoices, payment_method_ok, paddle_configured }.
POST/v1/orgs/{org}/billing/checkout POST/v1/orgs/{org}/billing/portal GET/v1/orgs/{org}/billing/invoices/{id}/pdf POST/v1/orgs/{org}/billing/subscriptions/{id}/cancel
Checkout { kind: "indie" | "payment_method", return_url } → { checkout_url }; Paddle customer portal { url }; invoice PDF { url }; cancel at period end.
Node agent#
POST /v1/agent/bootstrap | join | heartbeat | stopped and GET /install/node.sh are used by aurix-node-agent; they authenticate with the one-time join token and then the agent token. Not for direct use.
Health#
GET /healthz (process up) and GET /readyz (database and cell reachable). Public, unauthenticated.