Control API
The console, Control (enterprise) and public services use the same API origin (https://api.sectigoaxiom.com). This page summarises those endpoints. The authoritative source is the shared contract in docs/contracts/control-api.md and the zod schemas in apps/api/src/routes/*.ts and apps/api/src/control/*.ts. The agent-facing action contract is in the API reference.
Conventions
- Authentication. Console calls use the session cookie (
HttpOnly; Secure; SameSite=Lax, scoped to.sectigoaxiom.com) and, on every unsafe method, thex-axiom-csrfheader with the value fromGET /auth/session. Provider and CI calls can use a project API key (Authorization: Bearer axk_…) with the scopesprovider:authorize,project.readorevidence.read. - Errors are problem documents (Errors and reason codes). A caller without access gets
404. - Lists return
{ "items": [...] }; paged lists addnext_cursor. - Timestamps are RFC 3339 UTC.
Roles and permissions
Roles bind to a scope. Organisation roles also apply to the organisation's tenants and to projects enrolled into it (apps/api/src/auth/rbac.ts).
| Scope | Roles |
|---|---|
| org | org_admin, security_admin, app_owner, approver, auditor |
| tenant | tenant_admin, finance_approver, approver, auditor |
| project | project_owner, developer, viewer |
| Permission | Held by |
|---|---|
project.read | project_owner, developer, viewer, org_admin, security_admin, app_owner, auditor |
project.write | project_owner, developer, org_admin, app_owner |
project.admin | project_owner, org_admin |
policy.write | project_owner, developer, org_admin, security_admin |
policy.publish | project_owner, org_admin, security_admin |
tenant.read | tenant_admin, finance_approver, approver, auditor, org_admin, security_admin |
tenant.admin | tenant_admin, org_admin |
agent.write | tenant_admin, org_admin, security_admin |
grant.write | tenant_admin, org_admin |
approval.decide | finance_approver, approver (an approval names the exact role it needs) |
evidence.read | project_owner, developer, viewer, tenant_admin, auditor, org_admin, security_admin |
evidence.export | project_owner, tenant_admin, auditor, org_admin, security_admin |
org.read | org_admin, security_admin, app_owner, auditor, approver |
org.admin | org_admin |
incident.respond | org_admin, security_admin |
connector.write | org_admin, security_admin |
Sign-in and OAuth
| Method and path | Purpose |
|---|---|
GET /auth/session | Current user and CSRF token |
POST /auth/logout | End the session |
POST /auth/magic-link, POST /auth/magic-link/verify | Email magic link (15 minutes, rate-limited) |
POST /auth/passkeys/register/options · /register/verify · /login/options · /login/verify, DELETE /auth/passkeys/:id | WebAuthn passkeys |
POST /auth/sso/discover, GET /auth/sso/:id/start, GET /auth/sso/callback | Enterprise OIDC SSO, routed by email domain |
GET /.well-known/oauth-authorization-server | RFC 8414 metadata |
GET /.well-known/oauth-protected-resource, GET /.well-known/oauth-protected-resource/mcp | RFC 9728 metadata for the API and the Axiom MCP server |
GET /.well-known/jwks.json | Every Axiom signing key ever used, with axiom:role and axiom:status |
POST /oauth/register | Dynamic client registration (public clients, token_endpoint_auth_method: none) |
GET /oauth/authorize | Authorization code + PKCE (S256 only); redirects to the console consent page |
POST /oauth/token | client_credentials (agents, private_key_jwt + DPoP), authorization_code, refresh_token (single use) |
POST /oauth/revoke | RFC 7009 revocation |
OAuth scopes: actions:submit, actions:read, projects:read, projects:write, approvals:read.
Gateway projects, services and operations
| Method and path | Body | Permission |
|---|---|---|
GET /v1/projects / POST /v1/projects | { name, environment?: development|staging|production, sample?: boolean } | signed in |
GET /v1/projects/:pid | Project with services, usage, limits, last_receipt | project.read |
PATCH /v1/projects/:pid | { name?, environment?, spend_limit_cents? } | project.admin |
GET /v1/projects/:pid/members, POST /v1/projects/:pid/invitations | { email, role: project_owner|developer|viewer }; 402 quota_exceeded at the plan's collaborator limit | project.read / project.admin |
POST /v1/invitations/:id/accept | { token } (from the email link; the signed-in email must match) | signed in |
POST /v1/projects/:pid/services | { name, kind: http|mcp|both, upstream_url?, upstream_mcp_url?, upstream_auth?, enforcement_mode?: proxy|sdk|adapter, admitted_tenants?: string[]|"any", outcome_keys?: JWK[] } | project.write |
GET /v1/services/:sid, PATCH /v1/services/:sid | Service with operations / partial update | project.read / project.write |
POST /v1/services/:sid/operations | { name, description?, sensitivity?: read|write|sensitive_write, resource_type, material_fields[], summary_template, http_route?, mcp_tool?, idempotency_support?: atomic|lookup|none } | project.write |
GET /v1/projects/:pid/api-keys, POST, DELETE /v1/projects/:pid/api-keys/:kid | { name, scopes: (provider:authorize|project.read|evidence.read)[] }; the secret is shown once | project.read / project.admin |
POST /v1/projects/:pid/webhooks, GET | { url, events?: string[], agent_id?, tenant_id? } | project.write / project.read |
GET /v1/projects/:pid/actions?state=&agent=&cursor=&limit= | Action list, newest first | evidence.read |
GET /v1/actions/:id/timeline | Status, agent, decision trace, events, approvals with decisions, related actions, diagnosis | evidence.read (project or tenant) |
GET /v1/actions/:id, GET /v1/actions/:id/receipt?reveal= | Status and receipt bundle for people with evidence access | evidence.read |
A material field is { name, type: string|decimal|integer|boolean|currency, required?, approver_visible?, sensitive? }. http_route is { method, path, resource_id_from?: "path:resource_id"|"body:<field>"|"query:<name>"|"header:<name>", params_from?: body|query }; mcp_tool is { name, resource_id_arg, params_args: { field: argument } }.
Policies and the simulator
:kind is projects or orgs.
| Method and path | Body | Permission |
|---|---|---|
GET /v1/:kind/:sid/policies | All versions | project.read / org.read |
POST /v1/:kind/:sid/policies | { name, rules[], policy_id? } creates a draft version | policy.write |
POST /v1/:kind/:sid/policies/:pid/versions/:v/publish | { stage?: boolean, rollout_percent? } | policy.publish |
POST /v1/:kind/:sid/policies/:pid/rollback | { to_version, reason } | policy.publish |
POST /v1/:kind/:sid/policies/preview | { policy_id?, rules[] } → { sampled, changed[] } over the last 200 actions | policy.write |
POST /v1/:kind/:sid/overrides | { rule_id, reason, expires_at (≤ 30 days), tenant_id?, operation? } | policy.publish |
POST /v1/projects/:sid/simulate | { service_id, operation, tenant_id, agent_id?, grant_id?, resource, params, environment?, candidate? } | project.read |
A policy ID used under another scope's path (publish, rollback, or a new version via policy_id) returns 404. A staged version (stage: true) is recorded with its rollout_percent but is preview-only: only active versions are evaluated. An override created at project scope has project authority and can lift only project and grant rules; an organisation override has enterprise authority. Hard rules are never lifted. See Policy layers for the rule format.
Tenants, agents and grants
| Method and path | Body | Permission |
|---|---|---|
GET /v1/tenants, POST /v1/tenants | { name, region?: us|eu } | signed in |
PATCH /v1/tenants/:tid/trust-profile | { workload_issuers: { issuer, jwks_uri, audience }[], accepted_issuers?, status_freshness_seconds? } | tenant.admin |
GET /v1/tenants/:tid/members, POST, DELETE /v1/tenants/:tid/members/:uid/:role | { email, role: tenant_admin|finance_approver|approver|auditor } | tenant.read / tenant.admin |
GET /v1/tenants/:tid/agents, POST | { name, description?, owner_user_id?, owner_service?, publisher?, operator?, sponsor_user_id? } | tenant.read / agent.write |
GET /v1/agents/:aid | Agent with credentials and grants | tenant.read |
POST /v1/agents/:aid/credentials/challenge | → { challenge_id, nonce, sign: { typ: "axiom-pop+jws", payload }, expires_in: 600 } | agent.write |
POST /v1/agents/:aid/credentials | { kind: "jwk", public_jwk, challenge_id, proof, validity_seconds?, issue_certificate?, rotation_overlap_seconds? } or { kind: "spiffe"|"oidc", issuer, subject, validity_seconds? } | agent.write |
POST /v1/agents/:aid/credentials/:cid/revoke | { reason } | agent.write |
POST /v1/agents/:aid/status | { status: active|suspended|retired, reason } | agent.write |
PUT /v1/agents/:id/card/publication | { enabled, domain? } | agent.write |
GET /v1/tenants/:tid/grants, POST | { agent_id, service_id, operations[], resources?, rules?, delegable?, expires_at } | tenant.read / grant.write |
POST /v1/grants/:gid/revoke | { reason }; revokes every descendant grant | grant.write |
Approvals and connectors' webhooks
| Method and path | Body | Who |
|---|---|---|
GET /v1/approvals?status=pending | Approvals waiting on you, with you_are_excluded and overdue | signed in |
GET /v1/approvals/:id | { view, can_decide, reason? } | eligible approvers, tenant auditors and admins |
POST /v1/approvals/:id/decision | { decision: approved|denied, digest: "sha-256:…", comment? }. Refused for an approval routed to a connector: that decision is made in the connector's system and read back | eligible approver |
POST /v1/connectors/:id/webhook | { native_ref? | correlation_id? }; only triggers a server-to-server readback; unauthenticated by design, rate limited to 60 per minute per connector | the native system |
Organisations, enrollment and connectors
| Method and path | Body | Permission |
|---|---|---|
POST /v1/orgs, GET /v1/orgs, GET /v1/orgs/:oid | { name, region: us|eu } | signed in / org.read |
POST /v1/orgs/:oid/verification | { legal_name, domain } (Sectigo organisation validation through the CA port) | org.admin |
GET/POST /v1/orgs/:oid/members, DELETE /v1/orgs/:oid/members/:uid/:role | { email, role } | org.read / org.admin |
GET/POST /v1/orgs/:oid/sso-connections, PATCH/DELETE /v1/sso-connections/:id | { protocol: "oidc", issuer, discovery_url, client_id, client_secret_ref?, domains[], default_role? } | org.admin |
GET/POST /v1/orgs/:oid/tenants | { name, region } (MSP-managed tenants) | org.read / org.admin |
POST /v1/orgs/:oid/enrollments, GET | { project_id, requested_terms: { visibility, retention_days, policy_changes[], evidence_transfer } } | org.admin / org.read |
GET /v1/enrollments/:id, POST …/accept, POST …/decline | { note? } | the project's owner |
GET /v1/connector-catalog | Every connector type with declared operations, boundary and availability (implemented or declared; only ServiceNow is implemented) | signed in |
GET/POST /v1/orgs/:oid/connectors, PATCH/DELETE /v1/connectors/:id | { type, name, mode: simulated|live, config, secret_ref? } | org.read / connector.write |
POST /v1/connectors/:id/health | → { ok, detail, checked_at } | connector.write |
GET /v1/connectors/:id/simulated-requests, POST /v1/connectors/:id/simulate | { native_ref, decision: approved|rejected, approver_name, approver_id } | connector.write |
Fleet, incidents, evidence and SIEM
| Method and path | Body | Permission |
|---|---|---|
GET /v1/orgs/:oid/fleet | Projects with enrollment state, coverage, policy versions, gateways, executor, last contact, expected impact | org.read |
GET/POST /v1/projects/:pid/gateways | { name, mode: customer_hosted|adapter, adapter?: kong|apigee|aws_apigw, region?, public_jwk } | project.read / project.admin |
POST /v1/gateways/:gid/heartbeat | Gateway-signed compact JWS { gateway_id, policy_version_acked, acks[], iat, jti } → { bundle, content } | gateway key |
GET /v1/gateways/:gid/bundle | x-axiom-gateway-auth: <JWS> or project.read | gateway key / project.read |
POST /v1/gateways/:gid/status | { status: active|draining|retired, executor?, transfer_to? } | project.admin |
POST /v1/orgs/:oid/search | { q } (agent, credential thumbprint, grant, tenant, customer or action ID) | org.read |
POST /v1/orgs/:oid/incidents, GET | { title, severity, targets[], suspend_agents?, revoke_grants? } | incident.respond / org.read |
GET /v1/incidents/:id, POST /v1/incidents/:id/release, GET /v1/incidents/:id/bundle | release takes { reason } | org.read / incident.respond / evidence.export |
GET /v1/orgs/:oid/actions?q=&state=&tenant=&project=&cursor=&limit= | Cross-project search within the caller's scope | org.read |
POST /v1/orgs/:oid/exports, GET /v1/exports/:id | { project_ids?, tenant_ids?, from?, to?, reveal? } (max 1000 receipts) | evidence.export |
POST /v1/receipts/:rid/legal-hold | { hold, reason } | evidence.export |
GET /v1/orgs/:oid/inventory | Agents with discovery state (observed, registered, verified, enforced), source, owner | org.read |
GET/POST /v1/orgs/:oid/siem | { name, type: webhook|splunk_hec|sentinel|chronicle, url, secret_ref?, redaction, scope } | org.read / org.admin |
POST /v1/siem/:id/test, POST /v1/siem/:id/replay | replay takes { from_seq } | org.admin |
The behaviour behind these endpoints is described in the admin guide and the runbooks.
Free public services
No account needed; rate-limited per client address.
| Method and path | Purpose |
|---|---|
POST /v1/public/identity-check, GET /v1/public/identity-check/:id | Agent identity check |
POST /v1/public/identity-check/challenge, POST …/challenge/verify | Live key-possession challenge |
GET /v1/agents/:id/card | Axiom-signed agent card (opt-in per agent) |
GET /v1/public/ca/trust-anchors | Purpose-specific CA trust anchors for agent credentials |
POST /v1/public/monitors, GET /v1/public/monitors/:id, GET …/compare?from=&to=, POST/DELETE …/watch, GET …/badge.json?pin= | Change monitor |
GET /v1/public/fixtures | Reference receipt fixtures for independent verifiers |
GET /v1/capabilities, GET /healthz | Discovery and health |