Axiomancer
API reference — Overview

Admin authentication

Configure Axiom Overwatch admin authorization for human operators and machine clients.

Admin authorization is the separate operator gate for sensitive Axiom Overwatch API operations. It is not an API-key tier and it is not cron authentication. An authorized request can be made by a signed-in Supabase user whose identity or trusted role is configured for admin access, or by a configured machine key.

Admin authorization is implemented by the shared requireAuth helper. The endpoints in Admin-gated API operations call that helper directly; it does not look up or authorize database API-key tiers.

Authorization evaluation order

requireAuth evaluates an admin request in this order:

  1. It applies the admin-attempt rate limit: more than 10 attempts per minute from the same source IP receive 429.
  2. It checks that at least one of the four ADMIN_* authorization paths below is configured. If none is configured, it returns 403.
  3. It reads the current Supabase session user and checks the user's ID against ADMIN_USER_IDS.
  4. It checks the user's trusted app_metadata roles against ADMIN_ROLE_CLAIMS.
  5. It checks the supplied machine key against ADMIN_MACHINE_KEYS.
  6. It checks the same machine key against the legacy ADMIN_API_KEY fallback.

The first matching path authorizes the request. A nonmatching request returns 401; the protected route does not continue into its handler.

For a machine actor, the helper reads X-API-Key first. If that header is absent, it reads Authorization: Bearer <key>. Supplying both headers uses the X-API-Key value.

Human actors

Human authorization starts with the authenticated Supabase user for the request. ADMIN_USER_IDS matches the user's immutable ID. Role authorization reads only app_metadata, not user_metadata: the latter must not grant admin access because it is commonly user-writable. The accepted role fields are role, roles, admin_role, admin_roles, app_role, and app_roles; each may be a string or an array of strings.

Machine actors

Machine automation can send either supported key header. Machine-key comparison uses a digest and constant-time equality check. ADMIN_MACHINE_KEYS is checked before the legacy key, so an automation key present in both configurations is resolved as a machine-key authorization rather than a legacy authorization.

There is no route-specific scope grammar in the current implementation. Every matching value in ADMIN_MACHINE_KEYS authorizes every operation in the table below. A value such as scope:imports is only a literal key value unless it exactly matches the configured secret; it does not limit access to imports.

Environment variables

The four admin settings are comma-separated lists where noted. Empty list items are ignored and surrounding whitespace is removed. The examples below are deliberately fake placeholders, not usable credentials.

VariableFormat and fake exampleAuthorization semantics
ADMIN_USER_IDSComma-separated Supabase user IDs. Example: user-placeholder-001,user-placeholder-002User ID entries are lowercased after trimming and compared to the signed-in user's ID. A match is the first authorization path.
ADMIN_ROLE_CLAIMSComma-separated role names. Example: ops,adminEntries and candidate role strings are lowercased. A match must be in the signed-in user's app_metadata; user metadata is ignored.
ADMIN_MACHINE_KEYSComma-separated machine-key values. Example: example-machine-key-a-not-a-secret,example-machine-key-b-not-a-secretEntries are trimmed but remain case-sensitive. Any matching entry authorizes all admin-gated operations; the value can arrive in X-API-Key or Authorization: Bearer.
ADMIN_API_KEYOne legacy machine-key value. Example: legacy-example-not-a-secretA nonempty exact match is considered only after the three preferred paths. It remains supported for compatibility, but is the broad legacy fallback.

CRON_SECRET is deliberately separate. Cron routes use it only through isAuthorizedCronRequest, which accepts Authorization: Bearer <cron secret>; it does not authorize the admin-gated operations on this page.

Machine-key requests

Use one of the supported headers. The following command uses a fake placeholder and the audit-log operation as an example.

curl -X GET \
  -H "X-API-Key: example-machine-key-not-a-secret" \
  https://www.axiomoverwatch.io/api/v1/admin/audit-log

ADMIN_MACHINE_KEYS accepts more than one comma-separated value. This lets an operator configure a replacement machine key before removing an old one, but each configured value has the same full admin access described above.

Admin-gated API operations

These are the current direct call sites of requireAuth. Methods not listed here may have their own API-key, session, analyst, or public-access rules, but they are not protected by this admin helper.

Operations and configuration

MethodPathAdmin operation
GET/api/v1/admin/audit-logRead audit records, with filters and optional CSV output.
POST/api/v1/calibrationCreate or replace a manual vessel-type calibration.
POST/api/v1/congestion/refreshRequest a manual congestion-index refresh.
POST/api/v1/freight-ratesCreate or update a freight-rate record.
GET/api/v1/keysList API-key metadata; key hashes are not returned.
POST/api/v1/keysCreate a free, starter, or pro API key. This route does not create admin-tier keys.
POST/api/v1/pi-coverage/importBulk-import P&I coverage from a plain-text CSV body.
GET/api/v1/webhooksList configured webhooks without their secrets.
POST/api/v1/webhooksCreate a webhook.
POST/api/v1/webhooks/slack-testSend a test request to a Slack webhook URL.

Dashboard admin surfaces

The /dashboard layout requires a signed-in Supabase user. Within that authenticated dashboard, the current code has two dedicated admin surfaces:

  • /dashboard/admin/audit-log accepts a key in its client form and sends it as X-API-Key to GET /api/v1/admin/audit-log.
  • /dashboard/admin/pi-import accepts a key in its client form and sends it as X-API-Key to POST /api/v1/pi-coverage/import.

Those page components do not add a separate user-ID or role check before they render. The admin decision happens when the protected API operation calls requireAuth. The P&I page also loads /api/v1/pi-clubs without a key; that directory request is session-authenticated for a signed-in dashboard user and is not one of the admin-gated operations listed above.

Failure behavior

Admin authorization is fail-closed. The helper returns an error response before the route handler performs its sensitive work.

StatusResponse bodyWhen it occurs
429{ "error": "Too many auth attempts" }More than 10 attempts in one minute from the same source IP.
403{ "error": "Admin access not configured" }None of ADMIN_USER_IDS, ADMIN_ROLE_CLAIMS, ADMIN_MACHINE_KEYS, or ADMIN_API_KEY is configured.
401{ "error": "Unauthorized" }A configured admin path exists, but the session user and supplied machine key do not match any allowed path.

For example, a machine request with an unconfigured key receives:

Response

{ "error": "Unauthorized" }

The individual operation can return additional validation or service errors after authorization succeeds. Those responses are specific to the endpoint and do not mean that admin authorization was granted to an unauthenticated caller.

Moving off ADMIN_API_KEY

ADMIN_API_KEY remains in the code as a compatibility fallback, but the preferred configuration is an immutable user ID or trusted app-metadata role for people and ADMIN_MACHINE_KEYS for automation.

Configure the preferred paths

Add the required human identities to ADMIN_USER_IDS and/or trusted role names to ADMIN_ROLE_CLAIMS. Add each automation credential to ADMIN_MACHINE_KEYS.

Update machine callers

Send the new configured machine value through X-API-Key or Authorization: Bearer. When both ADMIN_MACHINE_KEYS and ADMIN_API_KEY are present, the machine-key check is evaluated first.

Remove the legacy fallback

After callers no longer depend on the legacy value, remove ADMIN_API_KEY. At least one remaining admin path must be configured or all protected routes will return 403.

Was this page helpful?

On this page