Protocol guide
x402 Agent Payments
Kilawatt Cloud supports x402, an open protocol for machine-to-machine payments built on the standard HTTP 402 Payment Required status code. Autonomous agents can pay for GPU capacity directly from their own wallet, per request, with no manual signup or API key setup. On the wallet's first successful payment, Kilawatt automatically creates a machine account and its API key.
This is a separate access path from Kilawatt's standard prepay wallet tiers (Starter, Pro, Scale). Both paths lead to the same underlying compute infrastructure; x402 is designed for agents and automated systems that want to pay as they go.
Protocol overview
How it works
x402 follows a four-step request/response cycle, all within a single logical transaction:
- 1
Agent sends a request to a Kilawatt x402 endpoint describing the compute it wants.
- 2
Kilawatt responds 402 Payment Required, returning the exact price and payment details needed to proceed.
- 3
Agent signs a payment for that exact amount from its own wallet and resends the request with payment attached.
- 4
Kilawatt verifies and settles the payment, then attempts provisioning and returns the resulting job details.
No manual signup or prior API key setup is required. The agent needs a funded wallet on Base holding USDC. Kilawatt creates the wallet's machine account and API key automatically on its first successful payment.
Endpoint
The x402 exec route
All x402 jobs are dispatched through a single endpoint:
POST https://www.kilawattcloud.dev/api/public/x402/execStep 1
Initial request
Send a POST request describing the compute job. No payment header is included on the first attempt.
{
"gpu_type": "nvidia-a10",
"card_count": 1,
"duration_seconds": 60,
"routing_policy": "lowest_cost",
"workload_type": "agent_exec"
}| Field | Description |
|---|---|
| gpu_type(string) | GPU model to provision. Default: nvidia-h100. |
| card_count(integer) | Minimum 1. Maximum: 8 for H100, H200, A100, and L40S; 4 for B200; 16 for workstation-class cards. |
| duration_seconds(number) | Requested runtime from 60 to 86400 seconds. Default: 600. |
| routing_policy(string) | lowest_cost, lowest_latency, or zero_quota. Default: lowest_cost. |
| workload_type(string) | agent_exec, fine_tune, or batch_inference. Default: agent_exec. |
Fields not listed above are ignored. This request has no field for source code, a command, a container image, or a workload payload. It provisions tracked GPU capacity for the selected duration and workload classification; it does not submit code to run.
Step 2
Payment challenge
If no valid payment is attached, Kilawatt responds with 402 Payment Required and a JSON body describing exactly what payment will satisfy the request.
{
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "40000",
"resource": "https://www.kilawattcloud.dev/api/public/x402/exec",
"description": "Kilawatt GPU compute: 1x nvidia-a10 for 60s",
"mimeType": "application/json",
"payTo": "0x...",
"maxTimeoutSeconds": 600,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"extra": {
"name": "USD Coin",
"version": "2"
}
}
]
}| Field | Description |
|---|---|
| network | Settlement network. v1 uses base; v2 uses eip155:8453. |
| maxAmountRequired | Price in the asset's smallest unit (USDC uses 6 decimals - 40000 = $0.04). |
| payTo | Wallet address to pay. Always read this from the live response; do not hardcode it. |
| asset | Contract address of the payment asset. Currently USDC on Base (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913). |
| maxTimeoutSeconds | 600 seconds. The signed payment authorization has a 10-minute validity window. |
Pricing is calculated dynamically from live provider cost at request time. It is not fixed and varies by GPU type, card count, duration, and available capacity.
Step 3
Signed payment and retry
The agent's wallet signs an EIP-3009 transferWithAuthorization payload for the exact amount requested, and resends the original request with the payment attached per the x402 spec.
In practice, most implementations use an x402-aware HTTP client that handles this automatically rather than constructing the payload by hand.
Kilawatt supports x402 v1 with the X-PAYMENT header and x402 v2 with PAYMENT-SIGNATURE and PAYMENT-REQUIRED headers. v2 identifies Base mainnet as eip155:8453. See the machine-readable OpenAPI document.
Step 4
Settlement and response
Once payment is verified and settled on-chain, Kilawatt attempts to provision the requested capacity and returns the resulting job details. The response status follows the provisioning result rather than being fixed to 200.
{
"id": "a7f59f0e-...",
"job_id": "a7f59f0e-...",
"instance_id": "bf841914-...",
"provider": "Kilawatt infrastructure",
"workload_type": "agent_exec",
"gpu_type": "nvidia-a10",
"card_count": 1,
"routing_policy": "lowest_cost",
"failover_hops": 0,
"billed_usd": 0.04,
"status": "provisioning",
"ready": false,
"health_status": "pending",
"payment": {
"transaction_hash": "0x...",
"network": "base",
"amount_usd": 0.04,
"payer": "0x..."
},
"api_key": "kw_live_..."
}The provider field is intentionally identical for every job and does not identify the upstream supplier.
On the wallet's first successful payment, Kilawatt creates one long-lived API key for that wallet's machine account and returns it in this response. Only its hash is stored, so the raw key cannot be shown again. Later payments from the same wallet omit the api_key field. The key can authenticate the wallet's balance endpoint and job receipt endpoint, including GET /api/public/v1/jobs/{job_id}. It can also authenticate standard v1 launch endpoints, which remain subject to the normal wallet-funding, rate, concurrency, and spend controls.
| Field | Description |
|---|---|
| id | Kilawatt job identifier. Normally the same value as job_id. |
| job_id | Kilawatt execution record identifier used by GET /api/public/v1/jobs/{job_id}. |
| instance_id | Kilawatt lifecycle-tracking identifier for the provisioned instance. |
| status | provisioning when the node accepted the job. Readiness is confirmed separately by health checks. |
| billed_usd | Exact job charge from the credited balance. The current x402 pricing path rounds this charge up to whole cents. |
| payment.amount_usd | Full signed amount settled on-chain and credited to the wallet account, expressed in whole cents. |
| payment.transaction_hash | On-chain settlement transaction, verifiable on BaseScan. |
| api_key | Present only on this wallet's first successful payment. This is the wallet account's long-lived key and cannot be shown again. |
GET /api/public/v1/jobs/{job_id}
Job receipt
Authenticate with Authorization: Bearer kw_live_.... The job must belong to the API key's account. The receipt returns the recorded fields below.
| Field | Description |
|---|---|
| job_id | Execution record identifier. |
| status | Status stored on the execution record. |
| provider | Kilawatt infrastructure. The provider field is intentionally identical for every job and does not identify the upstream supplier. |
| workload_type | Workload classification stored on the execution record. |
| gpu_type | GPU type stored on the execution record. |
| duration_seconds | Requested duration, returned as a number. |
| billed_usd | Recorded job charge, returned as a number; defaults to 0 when absent. |
| created_at | Execution record creation timestamp. |
| ready_at | Timestamp of the instance's first healthy and reachable check; null until such a check is recorded. |
| ended_at | Instance stopped_at timestamp, or null when absent. |
| runtime_seconds | Seconds from created_at to ended_at, rounded to the nearest second and clamped to at least 0; null until the instance has stopped and both timestamps are present. |
| health | Object containing instance_status, health_status, last_healthy_at, last_checked_at and incidents. |
| health.instance_status | Instance status, or null if no instance is found. |
| health.health_status | ended_<instance_status> when the instance is not running; otherwise its recorded health status, or unknown when absent. |
| health.last_healthy_at | Instance last_health_ok_at timestamp, or null when absent. |
| health.last_checked_at | Instance last_health_check_at timestamp, or null when absent. |
| health.incidents | Recorded job incidents ordered by started_at ascending; an empty array when none are returned. |
| health.incidents[].kind | Recorded incident kind. |
| health.incidents[].severity | Recorded incident severity. |
| health.incidents[].started_at | Recorded incident start timestamp. |
| health.incidents[].ended_at | Recorded incident end timestamp, which may be null. |
| health.incidents[].resolved | Recorded incident resolution flag. |
| health.incidents[].credited_usd | Incident credited_cents divided by 100, rounded to four decimal places; defaults to 0 when absent. |
| health.incidents[].detail | Public incident detail: scrubbed detail for xid, ecc and thermal, otherwise a generic infrastructure event description. |
| sla_credit_usd | Sum of incident credited_cents divided by 100, rounded to four decimal places. |
| net_billed_usd | billed_usd minus the summed incident credits in USD, rounded to four decimal places. |
Accounting
Signed amount and job charge
The agent signs the amount in the 402 challenge. On the paying retry, Kilawatt obtains a fresh live quote. If the signed amount no longer covers the job, the endpoint returns another 402 response with a price_changed error instead of settling that payment.
When the signed amount is sufficient, the full amount the agent signed settles on-chain and is credited to the wallet's machine-account balance. The job charge is then taken from that balance. The response's billed_usd is the exact job charge. In the current x402 pricing path it is rounded up to whole cents, and payment.amount_usd reflects the whole-cent on-chain payment that was signed and settled.
If the signed amount is greater than the current job price, the difference remains as credit on the wallet's account. It is not returned on-chain automatically. Returning unused x402 credit on-chain requires an operator-reviewed payout. An agent is never charged more than the amount it signs.
Jobs that cannot be placed are not charged. A job that is placed but never confirms ready is refunded to the wallet's account balance. Refunds and unused amounts stay as account credit and are not returned on-chain automatically.
Failure modes
Errors
If payment verification or settlement fails, Kilawatt returns a non-200 status with the same challenge structure and an error field describing the failure reason.
{
"x402Version": 1,
"error": "invalid_payload: contract call failed: unable to call contract: execution reverted",
"accepts": [ ... ]
}Common causes include insufficient USDC balance, an expired or malformed payment payload, or a signed amount that no longer covers the price. The accepts array is included on error responses so a client can inspect requirements and retry.
- 400 invalid_json: The body is not a JSON object. An empty body uses the defaults. Checked before any price is quoted.
- 415 unsupported_media_type: Content-Type is present and is not application/json.
- 400 unknown_gpu_type: gpu_type is not supported. The body lists valid_gpu_types.
- 400 wrong_payment_header: The payment's x402Version does not match the header it was sent in. v1 goes in X-PAYMENT, v2 in PAYMENT-SIGNATURE. Nothing is charged or settled.
- 400 size_not_available: A card count above 8 cannot be placed right now. No payment is taken.
- 402 price_changed: The signed amount no longer covers the job. In v2 the body also carries required_amount_usd.
- 402 insufficient_funds_or_invalid_signature: The paying wallet may not hold enough USDC on Base, or the signature is invalid.
- 409 payment_already_used: This payment authorization was already settled. Includes transaction_hash and, when known, job_id. Fetch the receipt instead of paying again.
- 405: GET, PUT, PATCH and DELETE return JSON with an Allow: POST, OPTIONS header.
- 503: No capacity can be priced or placed right now. No payment is taken; retry later.
Send an Idempotency-Key header on paid requests and reuse it on retries. If a paid response is lost, a retry with the same key and a new payment authorization returns the original job id and status instead of creating a second job. The one-time api_key is returned only in the wallet's first paid response.
Quick start
First call with a plain wallet key
The fastest way to a first call. Use a dedicated test wallet holding a few dollars of USDC on Base. Never use the private key of a wallet that holds other funds.
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "x402-fetch";
const account = privateKeyToAccount(process.env.TEST_WALLET_PRIVATE_KEY);
const fetchWithPayment = wrapFetchWithPayment(fetch, account);
const response = await fetchWithPayment(
"https://www.kilawattcloud.dev/api/public/x402/exec",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
gpu_type: "nvidia-a10",
card_count: 1,
duration_seconds: 60,
routing_policy: "lowest_cost",
workload_type: "agent_exec",
}),
}
);
console.log(await response.json());Install with npm i viem x402-fetch. A successful response confirms provisioning and returns job, payment and health metadata. It does not run your code.
Example
Reference implementation with a Coinbase CDP wallet (Node.js)
This example uses Coinbase's CDP SDK for wallet signing, viem for the wallet client, and x402-fetch to handle the challenge/retry cycle automatically.
import { CdpClient } from "@coinbase/cdp-sdk";
import { createWalletClient, http } from "viem";
import { base } from "viem/chains";
import { wrapFetchWithPayment } from "x402-fetch";
const cdp = new CdpClient({
apiKeyId: process.env.CDP_API_KEY_ID,
apiKeySecret: process.env.CDP_API_KEY_SECRET,
});
const account = await cdp.evm.getAccount({ address: process.env.WALLET_ADDRESS });
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
});
const fetchWithPayment = wrapFetchWithPayment(fetch, walletClient);
const response = await fetchWithPayment(
"https://www.kilawattcloud.dev/api/public/x402/exec",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
gpu_type: "nvidia-a10",
card_count: 1,
duration_seconds: 60,
routing_policy: "lowest_cost",
workload_type: "agent_exec",
}),
}
);
const data = await response.json();
console.log(data);The wallet must hold USDC on Base. Under the x402 exact scheme the wallet only signs a payment authorization and the facilitator submits the transfer, so the paying wallet does not need ETH for gas. If a client reports a gas error, email hello@kilawattcloud.dev.
Access models
Relationship to prepay wallet tiers
x402 and Kilawatt's standard API (Starter / Pro / Scale prepay tiers) both provision the same underlying GPU capacity and both enforce funding before a job is dispatched. They differ in how payment and account setup are established.
| Feature | Prepay tiers | x402 |
|---|---|---|
| Account setup | User signs up and creates a key | No manual signup or key setup; created automatically for the paying wallet |
| Payment timing | Deposit upfront drawn down per job | Paid per individual request |
| Best suited for | Ongoing usage / dashboard access | Autonomous agents / one-off or sporadic usage |
Notes
Implementation notes
- x402 settlement occurs via Coinbase's facilitator on Base mainnet. Transactions are publicly verifiable on-chain.
- Payment amounts are calculated from live provider cost at request time, inclusive of applicable margin.
- A successful response confirms provisioning and returns job, payment, and health metadata. The public API does not provide workload output, stdout, artifacts, or a command-execution result.
- This document describes the protocol as currently implemented. Supported GPU types, networks, and assets may expand over time.

