Supported payments
Vending is mainnet only. Setamount in USD with at most two decimal places, from 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
Vend a key
SendPOST /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:
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:- TypeScript — EVM
- TypeScript — Solana
- curl — inspect 402
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
POST https://merchant.payai.network/api/v1/keys/recoverwithout an authentication header.- Read the CAIP-122 challenge from the
PAYMENT-REQUIREDheader. Itsextensions["sign-in-with-x"]value containsinfoandsupportedChains;acceptsis empty because this is authentication, not a payment. - Choose the supported chain for the wallet, call
createSIWxPayload({ ...info, chainId, type }, signer)from@x402/extensions/sign-in-with-x, encode it withencodeSIWxHeader, and repeat thePOSTwithSIGN-IN-WITH-X: <encoded payload>. - A valid, unused challenge returns
201with{ apiKey, account }.
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.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: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:
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 asAuthorization: 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" }.
