Skip to main content

Deploy Agent (x402)

Which tool should I use?
  • CLI — interactive local workflow with ACU.
  • SDK — programmatic deploys from TypeScript/JavaScript with ACU.
  • Deploy Agent — pay with USDC on Base via x402. Best for AI agents and services without ACU accounts.

The Acurast Deploy Agent lets developers deploy computational jobs to the Acurast decentralized processor network directly from EVM chains (starting with USDC on Base). Via the deploy agent's HTTP API a simple x402 payment together with either a job specification or a VPS request will trigger the deployment for you.

This approach is ideal for:

  • AI agents using agentic wallets to deploy Acurast jobs from LLM clients
  • Backend services that need to programmatically deploy Acurast jobs without interacting with the Acurast parachain or managing ACU accounts
  • Anyone who wants a public SSH-reachable Ubuntu VPS on a decentralized processor — see Deploy a VPS below

Both approaches are outlined below.

Supported Chains

ChainChain IDToken
Base8453USDC

Use via LLM

npx skills add coinbase/agentic-wallet-skills

Recommended skills are at least:

◆  Select skills to install (space to toggle)
│ ◼ authenticate-wallet (Sign in to the wallet. Use when you or the user want to l...)
│ ◼ fund (Add money to the wallet. Use when you or the user want to...)
│ ◻ monetize-service
│ ◼ pay-for-service (Make a paid API request to an x402 endpoint with automati...)
│ ◻ query-onchain-data
│ ◼ search-for-service (Search and browse the x402 bazaar marketplace for paid AP...)
│ ◻ send-usdc
│ ◻ trade
│ ◼ x402 (Search for new services and make paid API requests using ...)

Choose your LLM client and whether to install globally. For example:

│   ○ Augment (.augment/skills)
│ ○ IBM Bob (.bob/skills)
│ ❯ ● Claude Code (.claude/skills)
│ ○ OpenClaw (skills)
│ ○ CodeBuddy (.codebuddy/skills)
│ ○ Command Code (.commandcode/skills)
│ ○ Continue (.continue/skills)
│ ○ Cortex Code (.cortex/skills)
◆  Installation scope
│ ○ Project
│ ● Global (Install in home directory (available across all projects))
◆  Installation method
│ ● Symlink (Recommended) (Single source of truth, easy updates)
│ ○ Copy to all agents

And proceed with installation.

Read more about skills.

After restarting your LLM client, you can set up your wallet and fund it from within your LLM client using

/fund

Then, deploy a job via

/pay-for-service deploy.acu.run
note

Hitting invalid_payload: contract call failed: execution reverted? See Troubleshooting.

A typical flow to deploy a job on Acurast
❯ /pay-for-service                                                                                                                                                                                                                                 

⏺ I'll check the wallet status first to make sure it's authenticated and ready for paid requests.

⏺ Bash(npx [email protected] status)
⎿ Wallet Server
✓ Running (PID: 33552)
… +4 lines (ctrl+o to expand)

⏺ The wallet is authenticated and ready.

To make a paid API request, I need to know which endpoint you want to call. Do you have a specific x402 service URL you'd like to use?

If not, I can help you search the bazaar marketplace for available paid services using the /search-for-service skill. What kind of service or API are you looking for?

❯ /pay-for-service deploy.acu.run

⏺ I see you want to use the deploy.acu.run service. Let me first check what endpoints and API documentation are available for this service.

⏺ Bash(npx [email protected] x402 pay https://deploy.acu.run --json)
⎿ {
"status": 200,
"statusText": "OK",
… +5 lines (ctrl+o to expand)

⏺ The root endpoint doesn't require payment. Let me check if there's documentation or an OpenAPI spec available.

⏺ Fetch(https://deploy.acu.run)
⎿ Received 4 bytes (200 OK)

⏺ Let me check if there's documentation in the current repository about this deploy agent service.

⏺ Read 1 file (ctrl+o to expand)

⏺ I found the documentation. The Acurast Deploy Agent at deploy.acu.run is an x402 service for deploying jobs to the Acurast decentralized cloud.

Service Details
┌──────────┬────────────────────────────────────────────┐
│ Field │ Value │
├──────────┼────────────────────────────────────────────┤
│ Endpoint │ https://deploy.acu.run/deploy │
├──────────┼────────────────────────────────────────────┤
│ Method │ POST │
├──────────┼────────────────────────────────────────────┤
│ Network │ Base (chain 8453) │
├──────────┼────────────────────────────────────────────┤
│ Payment │ USDC (dynamic pricing based on job reward) │
└──────────┴────────────────────────────────────────────┘
Required Parameters
┌────────┬────────┬────────────────────────────────────────────────────────┐
│ Field │ Type │ Description │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ script │ string │ IPFS hash of the script (e.g., ipfs://Qm...) │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ reward │ number │ Reward per execution in picoACU (1 ACU = 10¹² picoACU) │
└────────┴────────┴────────────────────────────────────────────────────────┘
Optional Parameters

- schedule - Job schedule (start/end times, interval, duration)
- slots - Number of processor slots (default: 1)
- runtime - NodeJS, NodeJSWithBundle, or Shell
- assignmentStrategy - Single or Competing
- allowOnlyVerifiedSources - Only verified processors (default: true)

---
Would you like me to deploy a job? If so, please provide:
1. IPFS script hash (e.g., ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2)
2. Reward amount in ACU (e.g., 0.0771 ACU)

❯ deploy ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2 with 0.0771 ACU reward

⏺ I'll deploy that script with 0.0771 ACU reward (77,100,000,000 picoACU).

⏺ Bash(npx [email protected] x402 pay https://deploy.acu.run/deploy -X POST -d '{"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2", "reward": 77100000000}'
--…)
⎿ {
"status": 200,
"statusText": "OK",
… +22 lines (ctrl+o to expand)

⏺ Deployment successful!
┌────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Field │ Value │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Job Hash │ 0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Explorer │ https://hub.acurast.com/explorer/extrinsic/0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payment TX │ 0x88053... on Base (chain 8453) │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payer │ 0xceA0d23336f285f9d85d6b0c3DE7103eAEB168F9 │
└────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Your job has been deployed to the Acurast decentralized cloud with a reward of 0.0771 ACU per execution.

Programmatically deploy

This approach uses Coinbase's awal tool directly, instead of via the LLM. This is a deterministic approach for dApps and backends.

  1. Set up a Coinbase Agentic Wallet

  2. Fund your agentic wallet, then validate with:

    npx awal balance

    Example output:

    Base
    ────────────────────────
    USDC $20.21
    ETH 0.0008
    WETH 0.0008

    You need both USDC (for the deploy) and a little ETH on Base (for gas) — see Troubleshooting if a payment fails with insufficient funds.

3. OPTIONAL: Query accepted tokens and pricing before deploying

You can query the deploy agent to see accepted tokens and the job's price before making a payment:

curl "https://deploy.acu.run/deploy" -X POST -H "Content-Type: application/json" -i -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}' | sed -n 's/^payment-required: //Ip' | tr -d '\r' | base64 -d | jq

Example response:

{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://deploy.acu.run/deploy",
"description": "Deploy Acurast job",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "79000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x9b442C40DAaC631F74de4094F2998E4aC016098A",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2"
}
}
],
"extensions": ...
}
  1. Deploy using the agentic wallet

    See First App Deployment to learn how to upload your script and receive an IPFS URL using the Acurast CLI.

    npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
    "script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
    "reward": 77136800000
    }'

    The agent handles deployment to the Acurast network once an x402 facilitator confirms the payment was successful.

    Example response:

    Response:
    {
    "success": true,
    "paymentTxHash": "x402-1776088198704",
    "payer": "unknown",
    "chainId": 8453,
    "acurastJobHash": "0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
    "explorerLink": "https://hub.acurast.com/explorer/extrinsic/0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
    "message": "Job deployed successfully"
    }

    The deploy agent has deployed the job under 5H3ChM1QgrYyGwzmyQ1QkdFCJxzakjWsbQN5tSGP8dkn2VCR.

Deploy a VPS (SSH tunnel)

Instead of a job spec, send { "vps": { "sshKey": "..." } }. The deploy agent generates a P-256 tunnel keypair, precomputes the <hash>.acu.run subdomain, submits a Shell-runtime job to Acurast, and returns the shell connect command before the processor has even started. Once a processor picks the job up (~1-3 min), it installs Ubuntu + dropbear inside a proot sandbox, opens the Acurast reverse tunnel targeting dropbear, and Let's Encrypt issues a real certificate for the precomputed subdomain.

The domain is knowable off-chain because the tunnel's clientId is hex(sha256(compressed_pubkey)[0:8]) and the agent controls the keypair — it injects the private key into the job as an encrypted env var after match.

Minimal deploy

sshKey, reward, and maxExecutionTimeInMs are the three required fields. reward is per-execution in picoACU and maxExecutionTimeInMs bounds how long the VPS runs — price the reward for that window (see VPS parameters).

npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"reward\": 48686320000,
\"maxExecutionTimeInMs\": 7200000
}
}"

Example response:

{
"success": true,
"acurastJobHash": "0x...",
"domain": "https://<hash>.acu.run",
"probeUrl": "https://deploy.acu.run/vps/probe?domain=<hash>.acu.run",
"sshCommand": "ssh -o \"ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443\" root@<hash>.acu.run",
"explorerLink": "https://hub.acurast.com/explorer/extrinsic/0x..."
}

Connect

The tunnel wraps traffic in TLS (that's what gives the Let's Encrypt cert on <hash>.acu.run), so SSH speaks through an openssl s_client ProxyCommand that terminates the outer TLS at the relay. Use -i + IdentitiesOnly=yes to force the exact key you submitted — otherwise a client with multiple keys in ~/.ssh/ may present a non-matching one first and hit Permission denied (publickey) before offering the right one.

One-liner:

ssh -o "ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443" \
-i ~/.ssh/id_rsa -o IdentitiesOnly=yes \
root@<hash>.acu.run

Ergonomic version — add this once to ~/.ssh/config:

Host *.acu.run
ProxyCommand openssl s_client -quiet -servername %h -connect %h:443
User root
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
StrictHostKeyChecking accept-new

Then forever after just:

ssh <hash>.acu.run
Key-only auth

The tunnel bundle runs dropbear with -s -g (no password auth advertised). If SSH_AUTHORIZED_KEY fails to reach the processor for any reason the job aborts rather than exposing a password-authenticated shell.

Poll for readiness

The deploy response returns immediately, but the processor still needs ~2-5 min to install Ubuntu, dropbear, and open the reverse tunnel. Poll GET /vps/probe?domain=<returned-domain> until ready: true:

curl -s "https://deploy.acu.run/vps/probe?domain=<hash>.acu.run" | jq
{
"domain": "<hash>.acu.run",
"ready": true,
"banner": "SSH-2.0-dropbear_2025.88",
"cert": {
"subject": "<hash>.acu.run",
"issuer": "YE2",
"notBefore": "...",
"notAfter": "..."
}
}

ready: false responses carry an error field (timeout, unrecognized banner, connection closed before banner, TLS handshake failures) — retry on an interval, don't hot-loop.

Expose an HTTP service on the same subdomain

Add httpPort to the request; the bundle then installs sslh in front of dropbear so the same <hash>.acu.run domain serves both shell and HTTP simultaneously. Byte-0 protocol sniffing routes shell traffic to dropbear and HTTP requests to your app on 127.0.0.1:<httpPort>.

npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"reward\": 48686320000,
\"maxExecutionTimeInMs\": 7200000,
\"httpPort\": 8080
}
}"

Response echoes it back as httpPort: 8080. Inside the VPS, bind your service on 127.0.0.1:8080 (or 0.0.0.0:8080). Browsers hitting https://<hash>.acu.run/ then get your app; the existing ProxyCommand recipe keeps working for shell access.

Tradeoffs:

  • Small (~200 ms) connect-time delay on new shell connections while sslh waits its --on-timeout fallback (imperceptible for interactive use).
  • One HTTP backend per subdomain. For multiple services, run nginx / caddy inside the VPS and reverse-proxy from there.
  • Adds ~5-10 s to first-boot on the processor for apt-get install sslh.

VPS parameters

FieldTypeDescription
sshKeystringREQUIRED OpenSSH public key line (e.g., ssh-ed25519 AAAA... user@host), installed into dropbear's /root/.ssh/authorized_keys.
rewardnumberREQUIRED Reward per execution in picoACU (1 ACU = 10¹² picoACU). No default — price it for maxExecutionTimeInMs (e.g. 48686320000 ≈ 0.049 ACU is priced for a 2 h window; scale roughly linearly for longer/shorter windows or processors will skip the job).
maxExecutionTimeInMsnumberREQUIRED How long the VPS runs before the processor tears it down, in ms (setup time counts). No default. Longer windows should scale reward proportionally.
httpPortnumberEnable HTTP multiplexing on the same subdomain via sslh. Must be an integer in 1024..65535. Bind your app on 127.0.0.1:<httpPort> inside the VPS.
imagestringImage preset. Currently only "ubuntu" (default) — Ubuntu 25.04 aarch64 proot-distro.
networkstringTarget network: "mainnet" (default) or "canary".
callbackUrlstringOptional webhook receiving log / started / error JSON events from the tunnel bundle. Useful for observing boot progress or catching install failures.
minMemorynumberMinimum total processor RAM in bytes (matched against the v1_ram_total benchmark pool).
minStoragenumberMinimum available processor storage in bytes (matched against v1_storage_avail).
minCpuScorenumberMinimum single-core CPU benchmark score (Geekbench-style; matched against v1_cpu_single_core).
minCpuMultiScorenumberMinimum multi-core CPU benchmark score (matched against v1_cpu_multi_core).
minProcessorReputationnumberMinimum processor reputation on a 0..1_000_000 scale. Default 0 (no reputation filter).
startAtobjectWhen the execution window opens — relative { "msFromNow": 180000 } or absolute { "timestamp": <unix-ms> }. Default { "msFromNow": 180000 } (3 min). Must leave ≥ 2 min lead time for a processor to match.
maxStartDelayMsnumberMaximum tolerated start-delay in ms. Default 30000 (30 s). Widen for slower processors that may not be ready in time.

Full job specification

Generate job specification

The easiest way to craft a job specification as JSON is using the Acurast CLI with the extra argument --only-upload to just output the IPFS URL:

acurast deploy --only-upload

You can provide a complete job specification for more control:

npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}'

The jobSpec object follows the standard Acurast job registration format:

FieldTypeDescription
scriptstringREQUIRED IPFS URL of an already-pinned script (ipfs://Qm...). The agent does not upload scripts — pin first (e.g. acurast deploy --only-upload) and pass the URL. Non-ipfs:// values are rejected with a 400.
allowedSourcesstring[] | nullProcessor whitelist (null = any)
allowOnlyVerifiedSourcesbooleanRequire attested processors
schedule.startTimenumberJob start time (Unix ms)
schedule.endTimenumberJob end time (Unix ms)
schedule.durationnumberExecution duration (ms)
schedule.intervalnumberExecution interval (ms)
schedule.maxStartDelaynumberMax acceptable start delay (ms)
memorynumberMax memory bytes (0 = default)
networkRequestsnumberMax network requests (0 = default)
storagenumberMax storage bytes (0 = default)
requiredModulesnumber[]Required modules (e.g., LLM)
assignmentStrategystring"Single" or "Competing"
slotsnumberNumber of processor slots
rewardnumberREQUIRED Reward per execution (picoACU)
minReputationnumberMin processor reputation (0-1000000)
runtimestring"NodeJS", "NodeJSWithBundle", or "Shell"

Troubleshooting

invalid_payload: contract call failed: execution reverted

Symptom: A deploy payment fails with invalid_payload: contract call failed: execution reverted. See coinbase/payments-mcp#30.

Workaround: For now, creating a new wallet account under a different email address might resolves the issue.

insufficient funds even though USDC balance is enough

Payments settle on Base, so the wallet needs a small amount of base ETH to cover gas — having only USDC is not enough. Per Coinbase, keep a little ETH on Base in the wallet alongside your USDC. Check with npx awal balance and top up ETH if it reads 0.