LOOMAL
SDK / API ReferencePayments

Pay

Pay any x402-protected URL using your project's wallet

Scope: payments:spend

Pay a single x402-protected URL. Loomal runs the full handshake on your project's wallet: discover the seller's HTTP 402 challenge, check your mandate caps, sign an EIP-3009 USDC transfer authorization off-chain on your Kernel smart account, retry with X-Payment, and record the result.

Your agent never holds a signing key — that lives in the project's wallet, gated by the mandate you set in the Pay tab.

Request

import { Loomal } from "@loomal/sdk";

const loomal = new Loomal({ apiKey: process.env.LOOMAL_API_KEY! });

const paid = await loomal.payments.pay({
  url: "https://seller.example.com/search",
});
if (!paid.ok) throw new Error(`${paid.code} — ${paid.message}`);

console.log(paid.content);  // what the seller returned
console.log(paid.txHash);   // on-chain settle hash
import os
from loomal import Loomal

loomal = Loomal(api_key=os.environ["LOOMAL_API_KEY"])
paid = loomal.payments.pay(url="https://seller.example.com/search")
if not paid["ok"]:
    raise RuntimeError(f"{paid['code']}{paid['message']}")

print(paid["content"])  # what the seller returned
print(paid["txHash"])   # on-chain settle hash
curl -X POST https://api.loomal.ai/v0/payments/pay \
  -H "Authorization: Bearer loid-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seller.example.com/search"
  }'

The endpoint returns HTTP 200 on ok: true, 402 on payment-related failures, and 503 on platform issues. The body always has the same { ok, ... } shape — branch on ok before reading other fields.

Body

FieldTypeRequiredDescription
urlstringYesThe x402-protected URL to pay. Must respond with HTTP 402 and a valid x402 challenge body.
dryRunbooleanNoIf true, run mandate + balance checks without signing or moving money. Returns the same success/failure shape with txHash: null.

Response — ok: true

{
  "ok": true,
  "status": 200,
  "content": { "results": [/* ... */] },
  "contentType": "application/json",
  "cost": {
    "amountUsdc": "0.05",
    "amountUsdcRaw": "50000",
    "network": "base"
  },
  "txHash": "0x<onchain-settle-tx-hash>",
  "payer": "0x<your-project-wallet>",
  "recipient": "0x<seller-wallet>",
  "resource": "https://seller.example.com/search",
  "balanceAfter": {
    "usdc": "9.95",
    "usdcRaw": "9950000"
  },
  "mandate": {
    "mandateId": "m_abc123",
    "spentTodayUsdcRaw": "50000",
    "dailyCapUsdcRaw": "1000000",
    "remainingTodayUsdcRaw": "950000",
    "validUntil": "2027-01-01T00:00:00.000Z"
  }
}
FieldDescription
statusHTTP status the seller returned after settlement (typically 200).
contentParsed JSON body if the seller returned application/json.
contentTextRaw body string when the response wasn't JSON.
cost.amountUsdcRawAmount in raw USDC units (6 decimals). Divide by 1,000,000 for the decimal value.
txHashOn-chain settle hash on Base. null if dryRun: true.
balanceAfterWallet balance after the call landed.
mandate.remainingTodayUsdcRawRemaining spend room for the current UTC day.

Response — ok: false

{
  "ok": false,
  "code": "mandate_per_call_exceeded",
  "message": "Price 0.50 USDC exceeds maxPerCallUsdc 0.10",
  "hint": "Raise maxPerCallUsdc on the mandate or pay a cheaper endpoint",
  "resource": "https://seller.example.com/search",
  "cost": { "amountUsdc": "0.50", "network": "base" }
}
FieldDescription
codeStable identifier — see the error code table below.
messageHuman-readable explanation.
hintOne-line remediation.
retryAfterMsPresent on transient failures. Wait this long before retrying.
costThe price the seller asked for, when discovered. Absent for failures that occur before the challenge is read.

Error codes

Group these into mandate, wallet, discovery, settlement, and system buckets. The code field is the durable contract — message and hint may change.

codeBucketCause
mandate_not_foundMandateNo active mandate on the project's wallet. Create one via POST /v0/payments/mandates or the Pay tab.
mandate_expiredMandateThe mandate's validUntil is in the past.
mandate_revokedMandateThe mandate was revoked.
mandate_per_call_exceededMandateThe seller's price exceeds maxPerCallUsdc.
mandate_daily_cap_exceededMandateToday's cumulative spend would exceed dailyCapUsdc.
session_key_not_installedWalletThe session key Loomal uses to sign hasn't been installed on the Kernel account yet. Usually transient on first run.
session_key_install_failedWalletInstallation failed. Retry.
wallet_not_provisionedWalletThe project doesn't have a wallet yet. Top up via the Pay tab to trigger provisioning.
balance_insufficientWalletWallet doesn't hold enough USDC to cover the seller's price.
url_not_x402DiscoveryThe URL didn't return a valid HTTP 402 challenge.
network_unsupportedDiscoveryThe seller asked for a chain Loomal doesn't support.
network_mismatchDiscoveryThe seller's network doesn't match the wallet's network.
payment_response_invalidDiscoveryThe seller's X-Payment-Response header was malformed.
settle_failedSettlementOn-chain settlement failed (gas, revert, etc.).
facilitator_unavailableSystemThe x402 facilitator is temporarily unavailable. Includes retryAfterMs. Returned as HTTP 503.
payments_disabledSystemPayments are disabled on this Loomal instance. Returned as HTTP 503.
unauthorizedSystemAPI key lacks payments:spend scope.

HTTP Errors

These come from auth and request validation, before the pay flow runs — they throw in the SDK rather than returning { ok: false }.

StatuserrorCause
400bad_requestMissing or invalid url
401unauthorizedMissing or invalid Authorization header
403forbiddenAPI key lacks payments:spend scope

Next

On this page