Console
Docs/Reference

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.
Example
curl -H "Authorization: Bearer $AURIX_API_TOKEN" https://aurix.run/v1/orgs/$ORG/projects

Auth & 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.