Skip to content

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, the x-axiom-csrf header with the value from GET /auth/session. Provider and CI calls can use a project API key (Authorization: Bearer axk_…) with the scopes provider:authorize, project.read or evidence.read.
  • Errors are problem documents (Errors and reason codes). A caller without access gets 404.
  • Lists return { "items": [...] }; paged lists add next_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).

ScopeRoles
orgorg_admin, security_admin, app_owner, approver, auditor
tenanttenant_admin, finance_approver, approver, auditor
projectproject_owner, developer, viewer
PermissionHeld by
project.readproject_owner, developer, viewer, org_admin, security_admin, app_owner, auditor
project.writeproject_owner, developer, org_admin, app_owner
project.adminproject_owner, org_admin
policy.writeproject_owner, developer, org_admin, security_admin
policy.publishproject_owner, org_admin, security_admin
tenant.readtenant_admin, finance_approver, approver, auditor, org_admin, security_admin
tenant.admintenant_admin, org_admin
agent.writetenant_admin, org_admin, security_admin
grant.writetenant_admin, org_admin
approval.decidefinance_approver, approver (an approval names the exact role it needs)
evidence.readproject_owner, developer, viewer, tenant_admin, auditor, org_admin, security_admin
evidence.exportproject_owner, tenant_admin, auditor, org_admin, security_admin
org.readorg_admin, security_admin, app_owner, auditor, approver
org.adminorg_admin
incident.respondorg_admin, security_admin
connector.writeorg_admin, security_admin

Sign-in and OAuth ​

Method and pathPurpose
GET /auth/sessionCurrent user and CSRF token
POST /auth/logoutEnd the session
POST /auth/magic-link, POST /auth/magic-link/verifyEmail magic link (15 minutes, rate-limited)
POST /auth/passkeys/register/options · /register/verify · /login/options · /login/verify, DELETE /auth/passkeys/:idWebAuthn passkeys
POST /auth/sso/discover, GET /auth/sso/:id/start, GET /auth/sso/callbackEnterprise OIDC SSO, routed by email domain
GET /.well-known/oauth-authorization-serverRFC 8414 metadata
GET /.well-known/oauth-protected-resource, GET /.well-known/oauth-protected-resource/mcpRFC 9728 metadata for the API and the Axiom MCP server
GET /.well-known/jwks.jsonEvery Axiom signing key ever used, with axiom:role and axiom:status
POST /oauth/registerDynamic client registration (public clients, token_endpoint_auth_method: none)
GET /oauth/authorizeAuthorization code + PKCE (S256 only); redirects to the console consent page
POST /oauth/tokenclient_credentials (agents, private_key_jwt + DPoP), authorization_code, refresh_token (single use)
POST /oauth/revokeRFC 7009 revocation

OAuth scopes: actions:submit, actions:read, projects:read, projects:write, approvals:read.

Gateway projects, services and operations ​

Method and pathBodyPermission
GET /v1/projects / POST /v1/projects{ name, environment?: development|staging|production, sample?: boolean }signed in
GET /v1/projects/:pidProject with services, usage, limits, last_receiptproject.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 limitproject.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/:sidService with operations / partial updateproject.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 onceproject.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 firstevidence.read
GET /v1/actions/:id/timelineStatus, agent, decision trace, events, approvals with decisions, related actions, diagnosisevidence.read (project or tenant)
GET /v1/actions/:id, GET /v1/actions/:id/receipt?reveal=Status and receipt bundle for people with evidence accessevidence.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 pathBodyPermission
GET /v1/:kind/:sid/policiesAll versionsproject.read / org.read
POST /v1/:kind/:sid/policies{ name, rules[], policy_id? } creates a draft versionpolicy.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 actionspolicy.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 pathBodyPermission
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/:aidAgent with credentials and grantstenant.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 grantgrant.write

Approvals and connectors' webhooks ​

Method and pathBodyWho
GET /v1/approvals?status=pendingApprovals waiting on you, with you_are_excluded and overduesigned 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 backeligible 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 connectorthe native system

Organisations, enrollment and connectors ​

Method and pathBodyPermission
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-catalogEvery 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 pathBodyPermission
GET /v1/orgs/:oid/fleetProjects with enrollment state, coverage, policy versions, gateways, executor, last contact, expected impactorg.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/heartbeatGateway-signed compact JWS { gateway_id, policy_version_acked, acks[], iat, jti } → { bundle, content }gateway key
GET /v1/gateways/:gid/bundlex-axiom-gateway-auth: <JWS> or project.readgateway 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/bundlerelease 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 scopeorg.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/inventoryAgents with discovery state (observed, registered, verified, enforced), source, ownerorg.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/replayreplay 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 pathPurpose
POST /v1/public/identity-check, GET /v1/public/identity-check/:idAgent identity check
POST /v1/public/identity-check/challenge, POST …/challenge/verifyLive key-possession challenge
GET /v1/agents/:id/cardAxiom-signed agent card (opt-in per agent)
GET /v1/public/ca/trust-anchorsPurpose-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/fixturesReference receipt fixtures for independent verifiers
GET /v1/capabilities, GET /healthzDiscovery and health

Developer preview. Contract version 2026-11-14.