Fleet, gateways and cutover
Control shows every enrolled project with its enforcement coverage, policy versions, gateways and last contact, and lets you move execution from the Sectigo-hosted gateway to a gateway you run without ever having two executors.
Console: Fleet and gateways at /control/orgs/:oid/fleet.
The fleet view
GET /v1/orgs/{oid}/fleet returns one item per project:
| Field | Meaning |
|---|---|
enrollment_state | Enrolled, or invited for a pending invitation (then only the project ID is shown). |
services | Protected services. |
enforcement_coverage | Share of services behind an enforcing path. |
policy_version, policy_versions[] | Policy versions in force. |
gateways[] | Each gateway with freshness, policy_version_acked, last_contact_at, executor. |
executor | The gateway ID that executes for this project, or hosted. |
last_contact_at | Most recent gateway contact. |
unacknowledged_changes | Policy or emergency-deny changes a gateway has not acknowledged yet. |
expected_impact | null or { staged_policies[], sampled, changed } for staged policies. |
Register a gateway
A gateway (customer-hosted, or an adapter in an existing API gateway) authenticates with its own key pair. Only the public key is registered.
POST /v1/projects/{project_id}/gateways
content-type: application/json
{ "name": "eu-west VPC gateway", "mode": "customer_hosted", "region": "eu-west-1", "public_jwk": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" } }For an adapter use "mode": "adapter" with "adapter": "kong" | "apigee" | "aws_apigw". Requires project.admin. A new gateway is staged: status: "active", executor: false. While no gateway holds the executor flag, the hosted gateway executes.
What exists in this release
The control-plane side (registration, signed heartbeats, policy bundles, acknowledgments, freshness, executor ownership) is implemented. The customer-hosted gateway runtime itself and the Kong, Apigee and AWS API Gateway adapters are not part of this repository yet, and none is certified. There is no API yet through which a customer-hosted gateway claims execution of an action.
Heartbeats and bundles
A gateway calls in with a compact JWS signed by its registered key over:
{ "gateway_id": "gwy_…", "policy_version_acked": "pol_…@3,pol_…@1", "acks": ["dny_…"], "iat": 1763143200, "jti": "hb-6f1c…" }POST /v1/gateways/{gateway_id}/heartbeat
content-type: application/jose
eyJhbGciOiJFUzI1NiIs…iatmust be within ±120 seconds and eachjtiis accepted once, so a captured heartbeat cannot be replayed to fake an acknowledgment.ackslists emergency-deny IDs the gateway now enforces; each new acknowledgment is audited.- The response is
{ bundle, content }:bundleis a compact JWS signed by Axiom'spolicykey;contentis its decoded claims (issued_at,fresh_until,policy_version,executor,policies[],trust,emergency_denies[]).
GET /v1/gateways/{gateway_id}/bundle returns the current bundle to the gateway (header x-axiom-gateway-auth with the same signed payload; acks ignored) or to a project reader.
Bundles are valid for 300 seconds. Freshness is fresh while the last bundle is valid, stale up to 15 minutes after the last contact, then offline. A partitioned gateway may evaluate operations allowed by a still-current bundle; once it is stale, sensitive writes must stay blocked or pending.
One executor per project
At most one gateway executes a project's actions. The flag moves in a single update, so a cutover or rollback can never leave two executors. Every change is audited with the executor before and after.
POST /v1/gateways/{gateway_id}/status
content-type: application/json
{ "status": "active", "executor": true }| Body | Effect |
|---|---|
{ "status": "active", "executor": true } | This gateway becomes the project's only executor (cutover, or rollback to it). |
{ "status": "draining" } on the executor | Execution moves back to the hosted gateway. Work this gateway already claimed stays with it. |
{ "status": "draining", "transfer_to": "gwy_…" } | Execution moves to another active gateway in the project. |
{ "status": "retired" } | Refused while the gateway is the executor or still has claimed actions executing. Retirement is final. |
The response includes project_executor. While a customer-hosted gateway holds the flag, no other path can claim the project's actions: every hosted path (REST POST /v1/actions/{id}/resume, the hosted HTTP proxy /p/{project}/http/*, the hosted MCP proxy /p/{project}/mcp and the Axiom MCP server) refuses to claim with 409 invalid_state ("execution for this project is owned by gateway …; resume through it"). An action allowed within a standing grant is still recorded: through the REST API or the Axiom MCP server it stays approved (202) for the executor gateway instead of being dispatched by the hosted gateway; the hosted proxies answer the 409. Embedded-SDK services are not affected by the flag: they execute inside the provider, which claims through POST /v1/authorize.
Cutover checklist
- Register the new gateway; confirm it reports
freshand acknowledges the currentpolicy_versionon the fleet screen. - Check policy parity with the simulator and test traffic.
- Restrict the upstream so protected routes are reachable only through the new enforced path.
- Make the new gateway executor:
{ "status": "active", "executor": true }. - Drain the old path: in-flight actions claimed before cutover finish where they were claimed; claims never move.
- Verify that an action pending approval from before the cutover still resolves exactly once, and that earlier receipts still verify.
- To roll back, make the hosted gateway executor again by draining the new gateway.