Deploy Flow
The MPP deploy flow uses HTTP 402 responses to communicate payment requirements. The agent calls the deploy endpoint, handles the 402, pays on-chain, and retries.
Step 1: Request Deploy (Get Quote)
Call POST /agent/deploy without a payment header. The gateway checks your credit balance and active projects:
POST /agent/deploy
Content-Type: application/json
X-Wallet-Address: 0x...
X-Signature: 0x...
X-Timestamp: 1711500000000
X-Nonce: unique-uuid
{
"uniqueName": "my-app",
"displayName": "My App",
"upload": {
"type": "files",
"files": [
{ "path": "index.js", "content": "base64..." },
{ "path": "package.json", "content": "base64..." }
]
}
}
Response Cases
Has credits, no active projects - deploys immediately (200):
JSON1{ "projectId": "uuid", "deploymentId": "uuid", "status": "deploying" }
Has credits, active projects exist - returns 402 with a warning:
JSON1{2 "error": "Payment required",3 "warning": "You have 2 active project(s) sharing credits...",4 "amount_usd": 0.5,5 "current_credit_balance_usd": 1.20,6 "active_projects": 2,7 "pay_to": "0x7EA5...",8 "payment_chain": "arbitrum",9 "token": "usdc",10 "decimals": 6,11 "supported_chains": [...]12}
To deploy using existing credits (reduces runtime of other projects), retry with header X-Use-Existing-Credits: true.
No credits - returns 402 with payment details:
JSON1{2 "error": "Payment required",3 "amount_usd": 0.5,4 "amount_token": "500000",5 "current_credit_balance_usd": 0,6 "active_projects": 0,7 "pay_to": "0x7EA5...",8 "payment_chain": "arbitrum",9 "token": "usdc",10 "decimals": 6,11 "supported_chains": [12 { "chain": "arbitrum", "chain_id": 42161, "tokens": ["usdc", "usdt"] },13 { "chain": "base", "chain_id": 8453, "tokens": ["usdc", "usdt"] }14 ]15}
Step 2: Check Balance
Before paying, verify the wallet has enough tokens:
GET /agent/balance/0xYourWallet?chain=arbitrum
JSON1{2 "address": "0x...",3 "chain": "arbitrum",4 "balances": [5 { "token": "usdc", "symbol": "USDC", "balance": "18.000000", "decimals": 6 }6 ]7}
If the balance is insufficient, prompt the user to fund the wallet on one of the supported chains.
Step 3: Pay On-Chain
Send an ERC20 transfer to the pay_to address from the 402 response:
TypeScript1import { createWalletClient, createPublicClient, http } from "viem";2import { arbitrum } from "viem/chains";34const ERC20_ABI = [{5 name: "transfer",6 type: "function",7 stateMutability: "nonpayable",8 inputs: [9 { name: "to", type: "address" },10 { name: "value", type: "uint256" },11 ],12 outputs: [{ name: "", type: "bool" }],13}] as const;1415const txHash = await walletClient.writeContract({16 address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum17 abi: ERC20_ABI,18 functionName: "transfer",19 args: [quote.pay_to, BigInt(quote.amount_token)],20});2122await publicClient.waitForTransactionReceipt({ hash: txHash });
Step 4: Deploy with Payment Proof
Retry the deploy request with the transaction hash:
POST /agent/deploy
Content-Type: application/json
X-Payment-Tx: 0xTransactionHash
X-Payment-Chain: arbitrum
X-Payment-Token: usdc
X-Wallet-Address: 0x...
X-Signature: 0x...
X-Timestamp: 1711500000000
X-Nonce: new-unique-uuid
Same body as Step 1. Response:
JSON1{ "projectId": "uuid", "deploymentId": "uuid", "status": "deploying" }
Step 5: Poll Status
Poll the status endpoint every 5 seconds until the deployment is ready:
GET /agent/deploy/{projectId}/{deploymentId}/status
Possible responses:
| Status | Meaning |
|---|---|
deploying | Build/deploy in progress |
ready | Live at endpoint URL |
failed | Deployment failed with reason |
JSON1{ "status": "ready", "endpoint": "https://my-app.nodeops.network" }
Upload Types
Files - array of base64-encoded files:
JSON1{2 "type": "files",3 "files": [4 { "path": "index.js", "content": "base64..." },5 { "path": "package.json", "content": "base64..." }6 ]7}
Zip - base64-encoded zip archive:
JSON1{2 "type": "zip",3 "data": "base64-zip-content",4 "filename": "code.zip"5}
Exclude from uploads: node_modules/, dist/, build/, .next/, .env, .git/, __pycache__/, venv/