Confidential Agents API

The Confidential Agents API provisions and manages per-organization confidential VM (CVM) instances that run packaged agents such as OpenClaw. Each instance is a hardware-isolated workload running inside a Trusted Execution Environment, addressable over SSH and uniquely named under your organization subdomain.

Quickstart

This walkthrough creates an instance, waits until it is ready, reads its instance record, and deletes it. The examples use curl and jq.

1. Obtain credentials

Organizations and API keys are provisioned by Confidential. To request access, contact us.

When your organization is created, you receive:

Export both values before running the examples:

export API_BASE="https://api.confidential.ai"
export ORGANIZATION_SLUG="acme"
export CA_API_KEY="confai_live_replace_with_your_key"

2. Test your API key

Call the usage endpoint. A successful response returns a data.pricing object and a data.usage object for the current billing cycle.

curl -sS "$API_BASE/v1/usage" \
  -H "Authorization: Bearer $CA_API_KEY" \
  | jq .

3. Create an instance

Use any valid OpenSSH public key (ssh-ed25519, ssh-rsa, ecdsa-sha2-*, etc.). We recommend ssh-ed25519 for new keys; ssh-rsa is supported for backwards compatibility but should be at least 2048 bits.

export SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)"

CREATE_RESPONSE="$(
  curl -sS -X POST "$API_BASE/v1/instances" \
    -H "Authorization: Bearer $CA_API_KEY" \
    -H "Content-Type: application/json" \
    --data "$(jq -n --arg public_key "$SSH_PUBLIC_KEY" '{
      public_key: $public_key,
      agent: "openclaw",
      inference_mode: "default_gateway"
    }')"
)"

echo "$CREATE_RESPONSE" | jq .

export INSTANCE_NAME="$(echo "$CREATE_RESPONSE" | jq -r '.data.name')"
export INSTANCE_HOSTNAME="$(echo "$CREATE_RESPONSE" | jq -r '.data.hostname')"

The create response is 202 Accepted. The instance starts in provisioning.

4. Get instance info

curl -sS "$API_BASE/v1/instances/$INSTANCE_NAME" \
  -H "Authorization: Bearer $CA_API_KEY" \
  | jq .

The response includes fields such as name, status, agent, hostname, inference_mode, created_at, and ready_at.

5. Poll until the instance is ready

There are no webhooks for instance state changes. Poll GET /v1/instances/{name} until status becomes ready.

while true; do
  INSTANCE_RESPONSE="$(
    curl -sS "$API_BASE/v1/instances/$INSTANCE_NAME" \
      -H "Authorization: Bearer $CA_API_KEY"
  )"

  STATUS="$(echo "$INSTANCE_RESPONSE" | jq -r '.data.status')"
  echo "status=$STATUS"

  if [ "$STATUS" = "ready" ]; then
    export INSTANCE_HOSTNAME="$(echo "$INSTANCE_RESPONSE" | jq -r '.data.hostname')"
    break
  fi

  if [ "$STATUS" = "failed" ]; then
    echo "$INSTANCE_RESPONSE" | jq .
    exit 1
  fi

  sleep 15
done

echo "Instance is ready: $INSTANCE_HOSTNAME"

Once the instance is ready, connect over SSH with the private key that matches the public key from the create request:

ssh -i ~/.ssh/id_ed25519 "$INSTANCE_HOSTNAME"

6. Verify the CVM attestation

ccvm is a CLI that runs inside the CVM and validates the hardware attestation, TPM measurements, host-key fingerprints, and inference gateway attestation chain — confirming the CVM is what it claims to be and is connected to the expected gateway. Run it from within the SSH session immediately after claiming the instance.

ccvm verify

Example output:

[1/5] SEV-SNP Hardware              PASS
[2/5] TPM Attestation               PASS
[3/5] Host Key Binding              PASS
[4/5] Inference Provider            PASS
[5/5] External Access Lockout       PASS  (FAIL on staging — debug SSH access intentional)

The tool is open source: github.com/lunal-dev/confidential-cvm-cli.

7. Delete the instance

When you are done, delete the instance. The response is 202 Accepted with status: terminating. The instance reaches terminated asynchronously once Azure resources are cleaned up.

curl -sS -X DELETE "$API_BASE/v1/instances/$INSTANCE_NAME" \
  -H "Authorization: Bearer $CA_API_KEY" \
  | jq .

Endpoints

All endpoints are served from https://api.confidential.ai and versioned under /v1.

Create Instance

POST /v1/instances

Provisions a new confidential VM under your organization. Returns 202 Accepted with the generated instance name. Provisioning typically completes within a few minutes; poll the Retrieve Instance endpoint until status flips from provisioning to ready.

Headers

HeaderRequiredDescription
AuthorizationyesBearer <api-key>.
Idempotency-KeyrecommendedPrevents duplicate provisioning on retry. See below.
X-Correlation-IDoptionalClient-supplied tag echoed back on the response.

Idempotency-Key

Idempotency-Key prevents duplicate side effects when retrying POST /v1/instances. It is not a request ID and not a correlation ID — each retry still receives a fresh request_id.

Request body

{
  "public_key": "ssh-ed25519 AAAAC3... user@example",
  "agent": "openclaw",
  "inference_mode": "default_gateway",
  "inference_model": "<model-id>"
}
FieldTypeRequiredDescription
public_keystringyesOpenSSH-formatted public key (ssh-rsa, ssh-ed25519, ecdsa-sha2-*, etc.). We recommend ssh-ed25519 for new keys; ssh-rsa is accepted for backwards compatibility.
agentstringnoPackaged agent to install. Defaults to openclaw. Currently openclaw is the only supported value.
inference_modestringnoInference routing mode. If omitted, the platform selects one for you. Currently default_gateway is the only supported value.
inference_modelstringnoModel identifier to use through the default inference gateway. If omitted, the platform selects a model for you and returns the chosen identifier in every subsequent response for this instance. Specify this field only if you need a particular model.

Response — 202 Accepted

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "data": {
    "name": "4k9p2xq7",
    "status": "provisioning",
    "agent": "openclaw",
    "hostname": "4k9p2xq7.acme.confidential.ai",
    "public_key": "ssh-ed25519 AAAAC3... user@example",
    "inference_mode": "default_gateway",
    "inference_model": "<model-id>",
    "egress_limit_bytes": 5368709120,
    "created_at": "2026-05-01T20:14:22Z"
  }
}

Once status reaches ready, connect over SSH:

ssh <hostname>

If your SSH client requires an explicit user or key path:

ssh -i <private-key-path> <hostname>

Errors

CodeHTTPWhen
invalid_request400public_key is missing or not a valid OpenSSH public key, or an unsupported agent or inference_mode was supplied.
unauthenticated401Missing or invalid Bearer token.
conflict409The same Idempotency-Key was reused with a different request body.

List Instances

GET /v1/instances

Returns all instances belonging to your organization, including terminated instances retained for the 30-day name-reservation window.

Response — 200 OK

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "data": {
    "instances": [
      {
        "name": "4k9p2xq7",
        "status": "ready",
        "agent": "openclaw",
        "hostname": "4k9p2xq7.acme.confidential.ai",
        "public_key": "ssh-ed25519 AAAAC3... user@example",
        "inference_mode": "default_gateway",
        "inference_model": "<model-id>",
        "egress_limit_bytes": 5368709120,
        "failure_code": null,
        "failure_message": null,
        "terminated_reason": null,
        "created_at": "2026-05-01T20:14:22Z",
        "ready_at": "2026-05-01T20:18:33Z",
        "terminated_at": null
      }
    ],
    "next_cursor": null
  }
}

See the Status enum under Retrieve Instance below for the full list of status values and field semantics.

Pagination

The response includes next_cursor, currently always null. Cursor pagination will be activated in a future release; the response shape is stable.

Retrieve Instance

GET /v1/instances/{name}

Returns the full record for one instance. Use this endpoint to poll provisioning status and to read current state.

There are no webhooks for instance state changes — poll this endpoint instead.

Path parameters

ParameterDescription
nameThe 8-character generated instance name, e.g. 4k9p2xq7.

Response — 200 OK

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": "venice-job-2026-05-03-001",
  "data": {
    "name": "4k9p2xq7",
    "status": "ready",
    "agent": "openclaw",
    "hostname": "4k9p2xq7.acme.confidential.ai",
    "public_key": "ssh-ed25519 AAAAC3... user@example",
    "inference_mode": "default_gateway",
    "inference_model": "<model-id>",
    "egress_limit_bytes": 5368709120,
    "failure_code": null,
    "failure_message": null,
    "terminated_reason": null,
    "created_at": "2026-05-01T20:14:22Z",
    "ready_at": "2026-05-01T20:18:33Z",
    "terminated_at": null
  }
}

Status enum

ValueMeaning
provisioningInstance is being claimed or cold-started.
readySSH is reachable and claim-time setup has completed. Run in-CVM verification to confirm attestation before trusting the workload.
failedProvisioning failed terminally. failure_code and failure_message may be populated.
terminatingDELETE /v1/instances/{name} has been accepted and resource teardown is in progress. The instance transitions to terminated once Azure resources are fully released.
terminatedResources have been released. The record and name reservation are retained for at least 30 days.

Egress

Each instance has a hard 5 GB egress limit per its lifetime. The platform monitors per-VM network egress and enforces the limit at the instance firewall layer once the threshold is observed. egress_limit_bytes is reported on every instance record.

Errors

CodeHTTPWhen
not_found404Instance does not exist or belongs to another organization.
unauthenticated401Missing or invalid Bearer token.

Delete Instance

DELETE /v1/instances/{name}

Tears down the CVM and removes its DNS record. The database row is retained with a terminal status, and the name remains reserved for at least 30 days.

Returns 202 Accepted with status: terminating. Azure resource teardown completes asynchronously; the instance transitions to terminated once cleanup finishes. Poll GET /v1/instances/{name} until status is terminated if you need confirmation that resources have been released.

Path parameters

ParameterDescription
nameThe 8-character generated instance name, e.g. 4k9p2xq7.

Response — 202 Accepted

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "data": {
    "name": "4k9p2xq7",
    "status": "terminating",
    "agent": "openclaw",
    "hostname": "4k9p2xq7.acme.confidential.ai",
    "public_key": "ssh-ed25519 AAAAC3... user@example",
    "inference_mode": "default_gateway",
    "inference_model": "<model-id>",
    "egress_limit_bytes": 5368709120,
    "failure_code": null,
    "failure_message": null,
    "terminated_reason": "deleted_via_api",
    "created_at": "2026-05-01T20:14:22Z",
    "ready_at": "2026-05-01T20:18:33Z",
    "terminated_at": null
  }
}

Errors

CodeHTTPWhen
not_found404Instance does not exist or belongs to another organization.
unauthenticated401Missing or invalid Bearer token.

Get Usage

GET /v1/usage

Returns the current billing-cycle consumption summary for your organization — the same numbers that drive your invoice.

Response — 200 OK

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "data": {
    "pricing": {
      "period_start": "2026-04-15T14:32:11Z",
      "period_end": "2026-05-15T14:32:11Z",
      "subscription_cost_usd": "200.00",
      "instance_hours_included": 200,
      "overage_per_hour_usd": "0.45"
    },
    "usage": {
      "instance_hours_used": 142.7,
      "inference_cost_usd": "28.23",
      "egress_bytes": 1048576,
      "egress_limit_bytes": 5368709120,
      "egress_observed_at": "2026-05-01T20:14:22Z",
      "egress_blocked": false,
      "egress_block_applied_at": null
    }
  }
}

pricing fields

FieldDescription
period_start, period_endStart and end of the current billing window (ISO 8601). The window is anchored to your organization's signup time, not to calendar months.
subscription_cost_usdBase subscription cost for the period.
instance_hours_includedInstance-hours included in the base subscription.
overage_per_hour_usdCost per instance-hour beyond the included amount.

usage fields

FieldDescription
instance_hours_usedTotal instance-hours consumed this period across all instances.
inference_cost_usdInference spend this period through the default gateway.
egress_bytesMost recent observed egress total for the active instance(s).
egress_limit_bytesPer-instance hard egress limit.
egress_observed_atWhen the egress total was last sampled.
egress_blockedtrue if egress is currently blocked at the firewall.
egress_block_applied_atWhen the block was applied, or null.

Per-day and per-instance usage breakdowns are not currently exposed through the API.

Authentication

All endpoints require a Bearer token in the Authorization header:

Authorization: Bearer ca_<8 char lowercase alphanumeric>_<18 char lowercase alphanumeric>

Obtaining credentials

Organizations and API keys are provisioned by Confidential. To request access, contact us. Self-service signup through the public API is not currently available.

When your organization is created, you receive:

Authentication errors

A missing, malformed, or unknown token returns:

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "error": {
    "code": "unauthenticated",
    "message": "missing or invalid Bearer token"
  }
}

A valid token used against an organization or resource it is not authorized for returns forbidden (403). See the error code table under Conventions below.

Conventions

Response envelope

Every JSON response is wrapped in a consistent envelope. On success:

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": "venice-job-2026-05-03-001",
  "data": { }
}

On error:

{
  "request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
  "correlation_id": null,
  "error": {
    "code": "invalid_request",
    "message": "public_key is not a valid OpenSSH public key: ...",
    "param": "public_key"
  }
}

The same request_id is mirrored in the X-Request-ID response header. If you supplied an X-Correlation-ID, it is echoed back in X-Correlation-ID on the response.

Request IDs and Correlation IDs

The API generates the canonical request_id for every request. Clients cannot choose it. If you send X-Request-ID, it is ignored and a fresh server-side ID is generated.

To tag requests for your own tracking, send X-Correlation-ID:

X-Correlation-ID: venice-job-2026-05-03-001

Correlation IDs are not required to be unique and the server does not enforce a format.

Error codes

CodeHTTPMeaning
invalid_request400Malformed or invalid input.
unauthenticated401Missing or invalid Bearer token.
forbidden403Token is valid but not allowed for this organization or resource.
not_found404Resource does not exist, or belongs to another organization.
conflict409State or idempotency conflict.
internal_error500Unexpected server error. Quote request_id when reporting.

Per-organization subdomain

Each organization is assigned a slug at onboarding and gets a dedicated subdomain:

{customer-slug}.confidential.ai

All instances are addressable as:

{instance-name}.{customer-slug}.confidential.ai

Example:

4k9p2xq7.acme.confidential.ai

Instance names

Instances are identified by an auto-generated name scoped to your organization.

Need help?

Contact us for access, support, or feature requests.