# SecretVM REST API for Agents (x402)

## Agent API Flow

This document describes how an agent that owns an EVM wallet calls the API to top up, check balance, create a VM, and read VM status.

### Authentication (x-agent headers)

Every agent request must include:

* `x-agent-address`: EVM wallet address.
* `x-agent-signature`: signature of the request hash.
* `x-agent-timestamp`: unix timestamp string (milliseconds preferred).

#### Request hash and signature

1. `method` = uppercase HTTP method (e.g. `GET`).
2. `path` = request path only (e.g. `/api/agent/balance`, no query string).
3. `body` = exact request body string (empty for GET). For JSON, use stable key ordering so the signed string matches the transmitted body.
4. `timestamp` = unix time string.
5. `payload` = `${method}${path}${body}${timestamp}`.
6. `request_hash` = SHA-256 hex of `payload`.
7. `signature` = `signMessage` of the hash bytes.

The server stores request hashes and rejects replays, so each request must use a fresh timestamp.

#### Multipart signature for create-vm

`POST /api/vm/create` is `multipart/form-data`. Instead of signing the raw multipart body, sign a stable JSON string:

```
{
  "fields": { ...form fields... },
  "file": {
    "fieldname": "dockercompose",
    "originalname": "docker-compose.yml",
    "mimetype": "application/x-yaml",
    "size": 123,
    "sha256": "<sha256 hex of file bytes>"
  }
}
```

Use sorted keys (stable stringify) to match server verification. This is the same structure used by the reference script.

### Typical agent flow

1. Top up (x402): `POST /api/agent/add-funds`.
2. Check balance: `GET /api/agent/balance`.
3. Create VM: `POST /api/vm/create`.
4. Poll status: `GET /api/agent/vm/:id`.

### Endpoint details

#### POST /api/agent/add-funds (top up)

Auth required.

**x402 payment (USDC)**

* Call with `amount_usdc` in the JSON body or query.
* If payment is required, server returns 402 with payment details.
* Retry with the `payment-signature` (or `x-payment`) header generated by your x402 client.
* On success, response includes:

```
{ "balance": "<minor units string>", "payment_method": "x402" }
```

#### GET /api/agent/balance

Auth required.

Response:

```
{ "balance": "<minor units string>" }
```

#### POST /api/vm/create (create VM)

Auth required.

`multipart/form-data`:

Required fields:

* `name`
* `vmTypeId`
* `dockercompose` file

Optional fields (passed as form fields):

* `inviteCode`
* `secrets_plaintext`, `secrets_encrypted`, `secrets_key`
* `docker_credentials_encrypted`, `docker_credentials_key`
* `fs_persistence`
* `custom_domain`
* `skip_launch`
* `description`
* `environment`
* `private`
* `dev_token`
* `skip_attest`
* `kms_provider`
* `platform`
* `cloudflareApiKey`
* `eip8004_registration` (JSON string)

Notes:

* Agent requests must have a balance >= `AGENT_MIN_BALANCE` (default 100 = 0.0001 USDC with 6 decimals).
* `upgradeability` is currently forced to `true` for create requests.
* `skip_attest` defaults to `"1"` for agent requests if omitted.

Response (subset):

```
{
  "id": "...",
  "name": "...",
  "created_at": "...",
  "updated_at": "...",
  "vm_type": "...",
  "vm_uid": "...",
  "vmDomain": "...",
  "skip_launch": false
}
```

#### GET /api/agent/vm/:id (VM status)

Auth required.

Response:

```
{
  "id": "...",
  "name": "...",
  "status": "...",
  "vmDomain": "...",
  "vmId": "...",
  "vmUid": "...",
  "created_at": "...",
  "updated_at": "..."
}
```

404 if the VM does not exist or does not belong to the agent.

### Examples

The header values are per-request. Recompute the signature whenever the method, path, body, or timestamp changes.

### Base URL

* `AGENT_BASE_URL=https://secretai.scrtlabs.com`

#### Build headers (Python)

```python
import hashlib
import json
import os
import time

import requests
from eth_account import Account
from eth_account.messages import encode_defunct

def stable_stringify(value):
    return json.dumps(value, sort_keys=True, separators=(",", ":"))

def build_headers(private_key, method, path, body):
    timestamp = str(int(time.time() * 1000))
    payload = f"{method}{path}{body}{timestamp}"
    request_hash = hashlib.sha256(payload.encode()).hexdigest()
    message = encode_defunct(hexstr=request_hash)
    signature = Account.sign_message(message, private_key).signature.hex()
    if not signature.startswith("0x"):
        signature = f"0x{signature}"
    address = Account.from_key(private_key).address
    return {
        "x-agent-address": address,
        "x-agent-signature": signature,
        "x-agent-timestamp": timestamp,
    }

def build_create_vm_headers(private_key, fields, file_bytes, file_name, mime):
    meta = {
        "fieldname": "dockercompose",
        "originalname": file_name,
        "mimetype": mime,
        "size": len(file_bytes),
        "sha256": hashlib.sha256(file_bytes).hexdigest(),
    }
    body = stable_stringify({"fields": fields, "file": meta})
    return build_headers(private_key, "POST", "/api/vm/create", body)

def create_vm(private_key, base_url, fields, file_bytes, mime):
    headers = build_create_vm_headers(private_key, fields, file_bytes, "docker-compose.yml", mime)
    return requests.post(
        f"{base_url}/api/vm/create",
        data=fields,
        files={"dockercompose": ("docker-compose.yml", file_bytes, mime)},
        headers=headers,
    )

def get_x402_payment_signature(challenge_json):
    sig = os.environ.get("X402_PAYMENT_SIGNATURE")
    if not sig:
        raise SystemExit("Set X402_PAYMENT_SIGNATURE from your x402 client")
    return sig

def topup_x402(private_key, base_url, amount_usdc):
    payload = {"amount_usdc": str(amount_usdc)}
    body = stable_stringify(payload)
    headers = build_headers(private_key, "POST", "/api/agent/add-funds", body)
    headers["Content-Type"] = "application/json"

    res = requests.post(f"{base_url}/api/agent/add-funds", data=body, headers=headers)
    if res.status_code == 402:
        payment_sig = get_x402_payment_signature(res.json())
        headers["payment-signature"] = payment_sig
        res = requests.post(f"{base_url}/api/agent/add-funds", data=body, headers=headers)
    return res

private_key = os.environ["AGENT_PRIVATE_KEY"].strip()
if not private_key.startswith("0x"):
    private_key = "0x" + private_key

base_url = os.environ.get("AGENT_BASE_URL", "https://secretai.scrtlabs.com").rstrip("/")
fields = {"name": os.environ["AGENT_VM_NAME"], "vmTypeId": os.environ["AGENT_VM_TYPE_ID"]}
amount_usdc = os.environ.get("AMOUNT_USDC", "1")

mime = os.environ.get("AGENT_DOCKER_COMPOSE_MIMETYPE", "application/x-yaml")
compose = """services:
  app:
    image: nginx:alpine
"""
file_bytes = compose.encode()

res = create_vm(private_key, base_url, fields, file_bytes, mime)
if res.status_code == 402 and "Insufficient balance" in res.text:
    print("Insufficient balance. Topping up via x402...")
    topup = topup_x402(private_key, base_url, amount_usdc)
    print("topup status:", topup.status_code)
    print("topup response:", topup.text)
    topup.raise_for_status()
    res = create_vm(private_key, base_url, fields, file_bytes, mime)

print("create status:", res.status_code)
print("create response:", res.text)
```

#### Balance (curl)

```bash
AGENT_BASE_URL = https://secretai.scrtlabs.com

curl -X GET "$AGENT_BASE_URL/api/agent/balance" \
  -H "x-agent-address: $AGENT_ADDRESS" \
  -H "x-agent-signature: $AGENT_SIGNATURE" \
  -H "x-agent-timestamp: $AGENT_TIMESTAMP"
```

#### Top up (curl, x402)

```bash
AGENT_BASE_URL = https://secretai.scrtlabs.com
body='{"amount_usdc":"1"}'

curl -X POST "$AGENT_BASE_URL/api/agent/add-funds" \
  -H "Content-Type: application/json" \
  -H "x-agent-address: $AGENT_ADDRESS" \
  -H "x-agent-signature: $AGENT_SIGNATURE" \
  -H "x-agent-timestamp: $AGENT_TIMESTAMP" \
  --data "$body"
```

If the response is 402, retry with the x402 `payment-signature` (or `x-payment`) header returned by your x402 client:

<pre class="language-bash"><code class="lang-bash">curl -X POST "$AGENT_BASE_URL/api/agent/add-funds" \
  -H "Content-Type: application/json" \
  -H "x-agent-address: $AGENT_ADDRESS" \
  -H "x-agent-signature: $AGENT_SIGNATURE" \
<strong>  -H "x-agent-timestamp: $AGENT_TIMESTAMP" \
</strong>  -H "payment-signature: $X402_PAYMENT_SIGNATURE" \
  --data "$body"
</code></pre>

#### VM status (curl)

```bash
curl -X GET "$AGENT_BASE_URL/api/agent/vm/$AGENT_VM_ID" \
  -H "x-agent-address: $AGENT_ADDRESS" \
  -H "x-agent-signature: $AGENT_SIGNATURE" \
  -H "x-agent-timestamp: $AGENT_TIMESTAMP"
```

#### Create VM (curl)

For multipart, the signature must be computed from the fields + file metadata shown in "Multipart signature for create-vm". The easiest path is to reuse `scripts/agent-create-vm.js`, but a raw curl request looks like:

```bash
curl -X POST "$AGENT_BASE_URL/api/vm/create" \
  -H "x-agent-address: $AGENT_ADDRESS" \
  -H "x-agent-signature: $AGENT_SIGNATURE" \
  -H "x-agent-timestamp: $AGENT_TIMESTAMP" \
  -F "name=my-vm" \
  -F "vmTypeId=TYPE_ID" \
  -F "dockercompose=@docker-compose.yml;type=application/x-yaml"
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.scrt.network/secret-network-documentation/secretvm-confidential-virtual-machines/agentic-support/secretvm-rest-api-for-agents-x402.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
