Skip to main content
Autonomous agents can pay mainnet USDC—or PAYAI on Solana at a 10% discount—over x402 and receive a facilitator API key plus credits in the same exchange. Each $1 of credit value buys 1,000 credits. The paying wallet becomes the account identity; paying again from the same wallet tops up that account and issues a fresh key.
The API key secret is returned once. It is never emailed and cannot be retrieved later, so store it securely before continuing. Purchased credits are non-refundable.

Supported payments

Vending is mainnet only. Set amount in USD with at most two decimal places, from 1upto1 up to 9,999.99 per payment; if omitted, it defaults to $1. A wallet that has no portal account becomes an agent account and receives a key plus credits; paying again from the same wallet tops it up and issues a fresh key. A wallet that is already linked to a human portal account receives credits only, and no agent key: keys for portal accounts are managed in the portal, and the recovery and key-management endpoints below answer 409 portal_account for such a wallet.

Discover the offer

The response is cached for five minutes and contains:

Vend a key

Send POST /api/v1/keys/vend?amount=5. The optional JSON body accepts only these documented fields:
recoveryEmail is normalized and may be at most 254 characters. keyName must be non-empty and may be at most 64 characters. The first request returns 402 Payment Required with its choices in the PAYMENT-REQUIRED header. Select one choice, create its exact x402 payment payload, and repeat the same request with the resulting PAYMENT-SIGNATURE header. After successful payment and settlement, the response is 201 Created with Cache-Control: no-store:
For a PAYAI payment, credits.paid.token is PAYAI, amountAtomic is the quoted token amount in its smallest unit, and usd is 90% of the purchased credit value. accountCreated is a best-effort pre-settlement value.

Runnable examples

Install the packages used by the example for your chain:
Set EVM_PRIVATE_KEY, then run this file with a TypeScript runner such as tsx. This example deliberately selects Base USDC.

Vending errors

Recovering a lost key

Recovery always issues a fresh key; it cannot reveal an old secret. You have three options.

Pay again

Repeat the vending flow from the same wallet. The payment adds credits to its existing account and returns a new key.

Sign with the paying wallet

  1. POST https://merchant.payai.network/api/v1/keys/recover without an authentication header.
  2. Read the CAIP-122 challenge from the PAYMENT-REQUIRED header. Its extensions["sign-in-with-x"] value contains info and supportedChains; accepts is empty because this is authentication, not a payment.
  3. Choose the supported chain for the wallet, call createSIWxPayload({ ...info, chainId, type }, signer) from @x402/extensions/sign-in-with-x, encode it with encodeSIWxHeader, and repeat the POST with SIGN-IN-WITH-X: <encoded payload>.
  4. A valid, unused challenge returns 201 with { apiKey, account }.
Alternatively, call client.registerExtension(createSIWxClientExtension({ signers: [signer] })) on an x402Client and use its HTTP transport handling.
In @x402/extensions 2.22, wrapFetchWithSIWx chooses a chain from accepts[0].network. It therefore does not handle this auth-only challenge, whose accepts array is empty. Use createSIWxClientExtension on an x402Client or sign the challenge directly as described above.
Each challenge expires after five minutes and its nonce can be used only once. If no account belongs to the signer, the endpoint returns 404 with error: "no_account_for_wallet" and a vend URL.

Use a recovery email

This works only if the email was previously attached to the account. Request a six-digit code:
The request always returns 202 { "ok": true } when its input is valid, whether or not the account and email match. This prevents account enumeration. For Solana, use "addressType": "svm" and a base58 wallet address. Confirm the code within its validity window:
A valid code returns 201 with { apiKey, account }. An invalid code returns 400 { "error": "invalid_code" }.

Managing the account with your key

The account API uses the same short-lived Ed25519 JWT described in Facilitator Authentication. Send it as Authorization: Bearer <jwt>. All responses use Cache-Control: no-store. For example, generate a JWT from the vended key ID and secret, then inspect the account:
generatePayAIJwt is the function from the TypeScript authentication example. Missing, invalid, or revoked credentials return 401 { "error": "unauthorized" }.