Quick start
- Connect your wallet and top up with USDG on Robinhood Chain Testnet from Billing.
- Create an agent under Agents. Copy its key (shown once).
- Call the API with the key, or plug it into an MCP client.
REST API
Charged to the agent owner's balance within the agent's caps. Failed runs are refunded.
curl -X POST https://veltrix.codes/api/v1/run/flux-schnell \
-H "Authorization: Bearer vx_live_..." \
-H "content-type: application/json" \
-d '{"prompt":"a red fox in snow, 35mm","image_size":"landscape_4_3"}'{
"id": "job_…",
"status": "succeeded",
"chargedMicro": 4200, // micro-USD, 4200 = $0.0042
"outputs": [{ "url": "https://…", "contentType": "image/jpeg", "kind": "image" }],
"balanceMicro": 9995800
}- GET /api/v1/models
- POST /api/v1/run/:model
- GET /api/v1/jobs/:id
- GET /api/v1/balance
x402 payments
Call /api/v1/run/:model without an API key and Veltrix answers 402 with the exact price. Sign one EIP-3009 TransferWithAuthorization for USDG and resend the request with an X-PAYMENT header. Veltrix submits it onchain and pays the gas. Network id: eip155:46630. If the run fails, the payment stays as Veltrix credit for your wallet.
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.PK);
const url = "https://veltrix.codes/api/v1/run/flux-schnell";
const body = JSON.stringify({ prompt: "a red fox in snow" });
// 1. ask for the price
const r1 = await fetch(url, { method: "POST", body, headers: { "content-type": "application/json" } });
const { accepts: [req] } = await r1.json(); // status 402
// 2. sign the authorization
const now = Math.floor(Date.now() / 1000);
const authorization = {
from: account.address, to: req.payTo, value: req.maxAmountRequired,
validAfter: "0", validBefore: String(now + 300),
nonce: "0x" + crypto.getRandomValues(new Uint8Array(32)).reduce((s, b) => s + b.toString(16).padStart(2, "0"), ""),
};
const signature = await account.signTypedData({
domain: { name: req.extra.name, version: req.extra.version, chainId: 46630, verifyingContract: req.asset },
types: { TransferWithAuthorization: [
{ name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" } ] },
primaryType: "TransferWithAuthorization",
message: { ...authorization, value: BigInt(authorization.value), validAfter: 0n, validBefore: BigInt(authorization.validBefore) },
});
// 3. pay and run
const payment = btoa(JSON.stringify({ x402Version: 1, scheme: "exact", network: req.network, payload: { signature, authorization } }));
const r2 = await fetch(url, { method: "POST", body, headers: { "content-type": "application/json", "X-PAYMENT": payment } });
console.log(await r2.json());MCP
Stateless Streamable HTTP. Tools: list_models, quote, generate, get_balance, get_job.
{
"mcpServers": {
"veltrix": {
"type": "http",
"url": "https://veltrix.codes/api/mcp",
"headers": { "Authorization": "Bearer vx_live_..." }
}
}
}claude mcp add --transport http veltrix https://veltrix.codes/api/mcp --header "Authorization: Bearer vx_live_..."
Model inputs
| flux-schnell | prompt · string ≤ 2000 · required image_size · landscape_4_3 | landscape_16_9 | portrait_4_3 | portrait_16_9 | square | square_hd num_images · int 1–4 seed · int 0–2147483647 |
| z-image-turbo | prompt · string ≤ 2000 · required image_size · landscape_4_3 | landscape_16_9 | portrait_4_3 | portrait_16_9 | square | square_hd num_images · int 1–4 seed · int 0–2147483647 |
| wan-2.2-5b | prompt · string ≤ 1500 · required |
| minimax-speech | text · string ≤ 5000 · required |
| trellis | image_url · https URL or data URI · required |
Errors
{ "error": { "code": "daily_cap_reached", "message": "Daily cap $5.00 would be exceeded" } }
400 invalid_input 401 invalid_api_key 402 insufficient_balance | daily_cap_reached | budget_exhausted
403 agent_paused | model_not_allowed 404 model_not_found 429 rate_limited
502 run failed (refunded) 503 models_not_configured | x402_not_configured