Confidential Agents API
The Confidential Agents API provisions and manages per-tenant 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 tenant 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
Tenants and API keys are provisioned by Confidential. To request access, contact us.
When your tenant is created, you receive:
- A tenant slug, used in instance hostnames such as
{instance-name}.{tenant-slug}.confidential.ai. - An API key, used as a Bearer token for API requests.
Export both values before running the examples:
export API_BASE="https://api.confidential.ai"
export TENANT_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 an ed25519 or ecdsa SSH public key. ssh-rsa keys are rejected.
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. Delete the instance
When you are done, delete the instance. The response is 202 Accepted; teardown may complete immediately with status: "terminated" or briefly report terminating.
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 tenant. 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
| Header | Required | Description |
|---|---|---|
Authorization | yes | Bearer <api-key>. |
Idempotency-Key | recommended | Prevents duplicate provisioning on retry. See below. |
X-Correlation-ID | optional | Client-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.
- A duplicate request within 24 hours returns the original result and does not provision a second CVM.
- The response body is the original API result, but
request_idandcorrelation_idreflect the current attempt. - Reusing the same
Idempotency-Keywith a different request body returns409 conflict.
Request body
{
"public_key": "ssh-ed25519 AAAAC3... user@example",
"agent": "openclaw",
"inference_mode": "default_gateway",
"inference_model": "<model-id>"
}
| Field | Type | Required | Description |
|---|---|---|---|
public_key | string | yes | OpenSSH-formatted public key. Must be ssh-ed25519 or ecdsa-sha2-*. ssh-rsa is rejected. |
agent | string | no | Packaged agent to install. Defaults to openclaw. Currently openclaw is the only supported value. |
inference_mode | string | no | Inference routing mode. If omitted, the platform selects one for you. Currently default_gateway is the only supported value. |
inference_model | string | no | Model 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> azureuser@<hostname>
Errors
| Code | HTTP | When |
|---|---|---|
invalid_request | 400 | public_key is missing or not an ed25519/ecdsa OpenSSH key, or an unsupported agent or inference_mode was supplied. |
unauthenticated | 401 | Missing or invalid Bearer token. |
conflict | 409 | The same Idempotency-Key was reused with a different request body. |
List Instances
GET /v1/instances
Returns all instances belonging to your tenant, 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
| Parameter | Description |
|---|---|
name | The 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
| Value | Meaning |
|---|---|
provisioning | Instance is being claimed or cold-started. |
ready | SSH is reachable and claim-time setup has completed. Run in-CVM verification to confirm attestation before trusting the workload. |
failed | Provisioning failed terminally. failure_code and failure_message may be populated. |
terminating | Delete is in progress. |
terminated | Resources 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
| Code | HTTP | When |
|---|---|---|
not_found | 404 | Instance does not exist or belongs to another tenant. |
unauthenticated | 401 | Missing 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. Teardown may complete synchronously, in which case the response shows status: "terminated". In other cases the instance briefly reports terminating and reaches terminated on subsequent retrieval.
Path parameters
| Parameter | Description |
|---|---|
name | The 8-character generated instance name, e.g. 4k9p2xq7. |
Response — 202 Accepted
{
"request_id": "4f1f6b6ab27d49bdb1a6a7a21c9f3b42",
"correlation_id": null,
"data": {
"name": "4k9p2xq7",
"status": "terminated",
"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": "2026-05-01T20:24:22Z"
}
}
Errors
| Code | HTTP | When |
|---|---|---|
not_found | 404 | Instance does not exist or belongs to another tenant. |
unauthenticated | 401 | Missing or invalid Bearer token. |
Get Usage
GET /v1/usage
Returns the current billing-cycle consumption summary for your tenant — 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
| Field | Description |
|---|---|
period_start, period_end | Start and end of the current billing window (ISO 8601). The window is anchored to your tenant's signup time, not to calendar months. |
subscription_cost_usd | Base subscription cost for the period. |
instance_hours_included | Instance-hours included in the base subscription. |
overage_per_hour_usd | Cost per instance-hour beyond the included amount. |
usage fields
| Field | Description |
|---|---|
instance_hours_used | Total instance-hours consumed this period across all instances. |
inference_cost_usd | Inference spend this period through the default gateway. |
egress_bytes | Most recent observed egress total for the active instance(s). |
egress_limit_bytes | Per-instance hard egress limit. |
egress_observed_at | When the egress total was last sampled. |
egress_blocked | true if egress is currently blocked at the firewall. |
egress_block_applied_at | When 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
Tenants 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 tenant is created, you receive:
- A tenant slug (e.g.
acme), which is used in your per-tenant subdomain ({instance-name}.acme.confidential.ai). - One or more API keys of the form
ca_<prefix>_<secret>, whereprefixandsecretare lowercase alphanumeric strings (8 and 18 characters respectively). Treat these as secrets — anyone with the key can provision and delete instances against your tenant.
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 a tenant 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 must be an ed25519 or ecdsa OpenSSH public key",
"param": "public_key"
}
}
request_id— server-generated identifier for this HTTP request. Always present. Quote it when contacting support.correlation_id— the value of theX-Correlation-IDrequest header if you sent one, otherwisenull.error.param— present when the error maps cleanly to a single request field.error.message— safe to surface to end users.
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
| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | Malformed or invalid input. |
unauthenticated | 401 | Missing or invalid Bearer token. |
forbidden | 403 | Token is valid but not allowed for this tenant or resource. |
not_found | 404 | Resource does not exist, or belongs to another tenant. |
conflict | 409 | State or idempotency conflict. |
internal_error | 500 | Unexpected server error. Quote request_id when reporting. |
Per-tenant subdomain
Each tenant 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 tenant.
- Format: 8-character lowercase alphanumeric string (e.g.
4k9p2xq7). - Uniqueness: Names are unique within a tenant.
- Reuse: Names are not reused for at least 30 days after termination, so a stored reference cannot silently point at a different instance.
- Custom names: Customer-supplied instance names are not currently supported.
Need help?
Contact us for access, support, or feature requests.