Pay a Suede x402 endpoint in five steps

Suede AI sells music, video and image generation per call over x402. There is no account and no API key: your wallet signs a USDC payment on Base, and the same HTTP request carries it. This guide follows the flow the live endpoints at app.suedeai.ai run, checked on 2026-10-11.

What you need

  • USDC on Base mainnet in a wallet you control. The lowest-priced call, Agent Image, costs $0.15.
  • A way to sign EIP-712 typed data, such as viem, ethers or eth-account. The suede-ai Python SDK does the signing for you.
  • No ETH for gas. Under the x402 exact scheme the facilitator submits the transfer and pays the gas. Your signature fixes the amount and the recipient, and the facilitator cannot change either.

1. Read the price from the discovery document

Prices are enforced on the server, so read them before you sign. The x402 discovery document lists every paid resource:

curl -s https://app.suedeai.ai/.well-known/x402 \
  | jq -r '.resources[] | "\(.method) \(.resource) \(.priceUsd)"'

On 2026-10-11 it returned three resources:

POST https://app.suedeai.ai/create-music $0.50
POST https://app.suedeai.ai/agent/video $4.99
POST https://app.suedeai.ai/agent/image $0.15

The discovery page covers the other machine-readable formats, including OpenAPI, the product catalog and the MCP server.

2. Send the request and read the 402 challenge

Send the request you want to buy, with no payment attached:

curl -i -X POST https://app.suedeai.ai/agent/image \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"Album cover, neon city at night, rain on glass","aspectRatio":"1:1","outputFormat":"png"}'

The server answers 402 Payment Required. The challenge comes twice: as base64-encoded JSON in the PAYMENT-REQUIRED header, and as the same JSON in the body. Trimmed to the fields you sign against:

HTTP/2 402
content-type: application/json
payment-required: eyJ4NDAyVmVyc2lvbiI6IDIs...
suede-api-version: 1

{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "resource": { "url": "https://app.suedeai.ai/agent/image" },
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "maxAmountRequired": "150000",
    "amount": "150000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x10FF767043A1723E0BB5B9207bC37D3442cC9E4F",
    "maxTimeoutSeconds": 300,
    "extra": { "name": "USD Coin", "version": "2", "decimals": 6, "priceUsd": "$0.15" }
  }]
}
FieldWhat it tells you
networkeip155:8453 is Base mainnet.
assetThe USDC contract on Base. It is also the EIP-712 verifyingContract.
amountThe price in USDC base units. USDC has 6 decimals, so 150000 is $0.15.
payToThe address that receives the payment.
maxTimeoutSeconds300 seconds on all three endpoints.
extra.name, extra.versionThe EIP-712 domain name and version: "USD Coin" and "2".

To read the header copy instead of the body, decode it:

curl -s -o /dev/null -D - -X POST https://app.suedeai.ai/agent/image \
  -H 'Content-Type: application/json' -d '{"prompt":"test"}' \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' \
  | base64 -d | jq '.accepts[0]'

An unpaid request gets the 402 even when the body is empty, so the challenge does not mean your body is valid. Check it against the endpoint reference before you sign.

3. Sign an EIP-3009 authorization

USDC supports EIP-3009 transferWithAuthorization, so paying is a signature over EIP-712 typed data. You do not send a transaction yourself. Fill the typed data from the accepts entry:

domain
  name               "USD Coin"            accepts[0].extra.name
  version            "2"                   accepts[0].extra.version
  chainId            8453
  verifyingContract  0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913   accepts[0].asset

primaryType  TransferWithAuthorization
  from         address   your wallet
  to           address   0x10FF767043A1723E0BB5B9207bC37D3442cC9E4F   accepts[0].payTo
  value        uint256   150000                                       accepts[0].amount
  validAfter   uint256   a Unix time slightly in the past
  validBefore  uint256   a Unix time later than now + maxTimeoutSeconds
  nonce        bytes32   32 random bytes

For reference, the suede-ai SDK sets validAfter to 60 seconds before now and validBefore to now plus the larger of 600 seconds and maxTimeoutSeconds plus 60.

4. Retry with the PAYMENT-SIGNATURE header

Wrap the signature in an x402 version 2 PaymentPayload. The accepted field repeats the accepts entry you chose:

{
  "x402Version": 2,
  "resource": { "url": "https://app.suedeai.ai/agent/image" },
  "accepted": {
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "150000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x10FF767043A1723E0BB5B9207bC37D3442cC9E4F",
    "maxTimeoutSeconds": 300,
    "extra": { "name": "USD Coin", "version": "2", "decimals": 6, "priceUsd": "$0.15" }
  },
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "0xYourWallet",
      "to": "0x10FF767043A1723E0BB5B9207bC37D3442cC9E4F",
      "value": "150000",
      "validAfter": "1791746000",
      "validBefore": "1791746660",
      "nonce": "0x..."
    }
  }
}

Base64-encode that JSON and send the identical request again with it in PAYMENT-SIGNATURE. Add an X-Idempotency-Key: if you resend the same key and body within the cache window, you get the original response back without a second charge.

curl -i -X POST https://app.suedeai.ai/agent/image \
  -H 'Content-Type: application/json' \
  -H "PAYMENT-SIGNATURE: $PAYMENT" \
  -H 'X-Idempotency-Key: cover-art-0001' \
  -d '{"prompt":"Album cover, neon city at night, rain on glass","aspectRatio":"1:1","outputFormat":"png"}'

A paid call answers 202 Accepted with a job to poll. The discovery document gives this example body:

{
  "jobId": "image-job-example",
  "status": "queued",
  "provider": "suede",
  "pollUrl": "https://app.suedeai.ai/agent/image/image-job-example"
}

Paid calls are asynchronous by default because a render takes minutes and payment clients time out in about 30 seconds. To block until the asset is ready, add ?async=false to the URL. Only the literal value true selects asynchronous mode: any other value, including 1, yes and on, counts as false and blocks.

The same header also accepts a Skyfire pay+jwt or kya-pay+jwt token in place of an x402 payload.

5. Poll for the result

Polling is free: the job was paid for when it was created. Request pollUrl until the job reaches a final state.

curl -s https://app.suedeai.ai/agent/image/<jobId>
EndpointPoll routeFinished whenAsset field
/agent/imageGET /agent/image/{jobId}status is completed or failedimageUrl
/agent/videoGET /agent/video/{jobId}status is completed or failedvideoUrl
/create-musicGET /api/songs/{songId}model_version is no longer pendingaudio_url

For music, audio_url holds a placeholder cover image until the track is rendered, so check model_version, not audio_url. A value of failed is final. The errors page covers rate limits, retries and what is never charged.

Skip the signing code

The suede-ai Python SDK runs steps 2 to 4 for you and returns the 202 body:

pip install suede-ai
from suede_ai import SuedeClient

with SuedeClient(wallet_private_key=PRIVATE_KEY) as suede:
    job = suede.agent_image(prompt="Album cover, neon city at night, rain on glass")
    print(job["pollUrl"])

Sell your own x402 endpoint

The same rail works in the other direction. x402 Agent Builder: Get Paid Per Call: build an AI agent on a visual canvas, set a price per call, and get paid in USDC on Base through x402 once you enable payments. Dry runs need no wallet.

Sources

Every value on this page comes from these documents, fetched on 2026-10-11, and from live requests to the endpoints on the same day.