Ripley Pocket gives AI agents custodial XMR wallets on the edge. One REST API, four capabilities: instant agent-to-agent transfers (zero fees), external Monero withdrawals, cross-chain swaps to BTC/ETH/USDT/LTC, and deposit via any supported crypto (auto-converts to XMR balance).
Before making any calls, you need two things:
| Variable | Description | Example |
|---|---|---|
| ---------- | ------------- | --------- |
RIPLEY_URL | Base URL of the gateway | https://pocket.ripley.run or http://localhost:8787 |
API_KEY | Agent's API key (issued at registration or by admin) | rp-live-abc123xyz |
All agent endpoints authenticate via the X-API-KEY header. Alternatively, use Authorization: Bearer .
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
| -------- | ---------- | ------ | --------- |
POST | /register | None | Self-register a new agent |
GET | /balance | API key | Check XMR balance |
GET | /history | API key | Transaction history |
POST | /pay | API key | Send payment (3 modes) |
GET | /swaps | API key | List cross-chain swaps |
POST | /deposit/swap | API key | Deposit via BTC/ETH/USDT/LTC → auto XMR |
GET | /deposit/swaps | API key | List deposit swap history |
POST | /xmr402/pay | API key | Pay an XMR402 challenge (returns txid + proof) |
POST | /xmr402/proof | API key | Recover TX proof if initial generation failed |
No authentication required. Returns an API key and deposit address. The account is inactive until a minimum deposit of 0.01 XMR arrives.
curl -X POST $RIPLEY_URL/register \
-H "Content-Type: application/json" \
-d '{"name": "My Agent"}'
Response:
{
"agent_id": "agent-a1b2c3d4",
"api_key": "rp-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"deposit_address": "4AdUndXHHZ...",
"status": "pending_deposit",
"min_deposit": 0.01,
"message": "Send at least 0.01 XMR to your deposit address to activate."
}
Save the api_key — it is shown only once. The account activates automatically when XMR arrives at the deposit_address.
Rate limit: 3 registrations per IP per hour.
curl $RIPLEY_URL/balance -H "X-API-KEY: $API_KEY"
Response:
{"balance": 2.5, "agent_id": "agent-a1b2c3d4"}
Always check balance before making a payment to confirm sufficient funds.
POST /payThis single endpoint handles three scenarios based on destination and currency:
Instant, zero fees. Use when paying another AI agent.
curl -X POST $RIPLEY_URL/pay \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "agent-other-id",
"amount": 0.1,
"memo": "Payment for data analysis"
}'
Response:
{
"status": "paid",
"amount_deducted": 0.1,
"remaining_balance": 2.4,
"message": "Internal transfer complete. 0 network fees."
}
The destination is the recipient's agent_id. If you don't know it, the other agent needs to share it with you.
Send XMR to any Monero address. The address must start with 4 (main) or 8 (subaddress). Fee: 0.001 XMR.
curl -X POST $RIPLEY_URL/pay \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx...",
"amount": 1.0
}'
Response:
{
"status": "processing",
"amount_deducted": 1.001,
"fee": 0.001,
"remaining_balance": 1.499,
"message": "Withdrawal queued. Will be broadcasted to Monero network shortly."
}
The balance is deducted immediately (amount + fee). The actual broadcast is async.
Pay in BTC, ETH, USDT, LTC, or other supported currencies. Set currency to the target coin.
curl -X POST $RIPLEY_URL/pay \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"amount": 0.001,
"currency": "btc",
"network": "Mainnet"
}'
Response:
{
"status": "swap_processing",
"message": "Deducted 0.158432 XMR. Delivering 0.001 BTC to destination.",
"trade_id": "abc123",
"xmr_deducted": 0.158432,
"eta_minutes": 10,
"tracking_url": "https://api.kyc.rip/v2/exchange/status/abc123"
}
The amount is the amount of the target currency you want to deliver — not XMR. The system calculates the XMR cost automatically (exchange rate + 0.5% gateway fee). If there aren't enough funds, you get a 402 with the required_xmr field.
Supported networks: ethereum/erc20, trc20/tron, bsc/bep20, solana, polygon, arbitrum, base, lightning, ton, and more. Network names are case-insensitive and aliased automatically.
POST /deposit/swapThis is the easiest way to fund an agent account if you don't have XMR. Send BTC, ETH, USDT, LTC, or any supported currency — it's automatically converted to XMR and credited to your balance.
curl -X POST $RIPLEY_URL/deposit/swap \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "btc",
"amount": 0.001,
"network": "Mainnet"
}'
Response:
{
"status": "awaiting_deposit",
"message": "Send exactly 0.001 BTC to the address below. ~0.156700 XMR will be credited to your balance.",
"trade_id": "xyz789",
"deposit_address": "bc1q...",
"send_amount": 0.001,
"send_currency": "BTC",
"xmr_expected": 0.1567,
"eta_minutes": 15,
"tracking_url": "https://api.kyc.rip/v2/exchange/status/xyz789"
}
Flow:
POST /deposit/swap with how much BTC/ETH you want to senddeposit_address returnedGET /deposit/swaps or GET /balance to confirm arrivalThe amount is how much of the source currency you're sending (not XMR). A 0.5% gateway fee is deducted from the XMR received.
curl $RIPLEY_URL/deposit/swaps -H "X-API-KEY: $API_KEY"
Returns all deposit swaps with status: awaiting_deposit → processing → completed.
curl "$RIPLEY_URL/history?limit=20&offset=0" \
-H "X-API-KEY: $API_KEY"
Response:
{
"transactions": [
{
"id": 1,
"type": "deposit",
"amount": 2.5,
"fee": 0,
"status": "success",
"memo": "XMR deposit",
"created_at": "2026-03-17 12:00:00"
}
],
"agent_id": "agent-a1b2c3d4"
}
Transaction types: deposit, internal_send, internal_recv, withdraw, swap_debit.
curl $RIPLEY_URL/swaps -H "X-API-KEY: $API_KEY"
Returns all cross-chain swap trades with their current status.
| Operation | Fee |
|---|---|
| ----------- | ----- |
| Internal transfer | 0 |
| XMR withdrawal | 0.001 XMR (fixed) |
| XMR402 payment | 0.001 XMR (fixed, same as withdrawal) |
| Cross-chain swap (out) | 0.5% gateway fee + exchange spread |
| Deposit swap (in) | 0.5% gateway fee (deducted from XMR received) |
| Status | Meaning | Action |
|---|---|---|
| -------- | --------- | -------- |
| 206 | Payment sent, proof generation failed | Use POST /xmr402/proof to recover the proof |
| 400 | Bad request (missing/invalid fields) | Fix request body |
| 401 | Missing API key | Add X-API-KEY header |
| 402 | Insufficient funds | Check balance, reduce amount |
| 403 | Invalid key or inactive account | Verify key, or use /deposit/swap to fund and auto-activate |
| 429 | Rate limited | Wait and retry (max 20 mutations/min, 60 reads/min) |
| 503 | Swap/proof service unavailable | Retry after a delay |
On a 402 for swaps, the response includes required_xmr — the exact XMR amount needed.
On a 206 for XMR402, the txid from the response is needed for proof recovery — save it.
XMR402 is the stateless payment protocol for the machine economy. When a service returns HTTP 402 Payment Required, it includes a cryptographic challenge. Ripley Pocket acts as the Gateway — it handles the XMR transfer and TX proof generation so your agent doesn't need its own Monero wallet.
HTTP 402 with:```
WWW-Authenticate: XMR402 address="4AdUnd...", amount="100000000", message="hmac_nonce...", timestamp="1710000000"
```
address — Monero subaddress to pay (piconero amount in amount)message — HMAC nonce binding this payment to the specific request payload (intent binding)POST /xmr402/pay```
Authorization: XMR402 txid="abc123...", proof="InProofV1..."
```
POST /xmr402/paySynchronous endpoint — broadcasts the XMR transfer and returns the TX proof in one shot (~1-2 seconds).
curl -X POST $RIPLEY_URL/xmr402/pay \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": "4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx...",
"amount": 0.0001,
"message": "hmac_nonce_from_www_authenticate_header"
}'
Response:
{
"status": "paid",
"txid": "7c1e2a3b4d...",
"proof": "InProofV1TFh...",
"amount_paid": 0.0001,
"fee": 0.001,
"authorization": "XMR402 txid=\"7c1e2a3b4d...\", proof=\"InProofV1TFh...\""
}
The authorization field is the exact header value to use when retrying the 402'd request. Copy it directly into the Authorization header.
If proof generation fails (status 206, "status": "paid_no_proof"), the payment was sent on-chain but the cryptographic proof couldn't be generated immediately (usually because the TX is still propagating). Use the recovery endpoint:
POST /xmr402/proof — Recover TX Proofcurl -X POST $RIPLEY_URL/xmr402/proof \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"txid": "a567e82af696...",
"address": "8AdUndXHHZ6...",
"message": "hmac_nonce_from_original_request"
}'
Response:
{
"status": "proof_recovered",
"txid": "a567e82af696...",
"proof": "InProofV2...",
"authorization": "XMR402 txid=\"a567e82af696...\", proof=\"InProofV2...\""
}
Use the same txid, address, and message from the original /xmr402/pay request. If the TX is still propagating, wait a few minutes and retry. The endpoint retries automatically up to 3 times with backoff.
Key differences from POST /pay:
message (HMAC nonce) binds this payment to the specific request, preventing replay attacks/xmr402/proof to retry without paying againimport httpx
RIPLEY_URL = "https://pocket.ripley.run"
headers = {"X-API-KEY": "rp-live-your-key-here", "Content-Type": "application/json"}
# Check balance
bal = httpx.get(f"{RIPLEY_URL}/balance", headers=headers).json()
print(f"Balance: {bal['balance']} XMR")
# Pay another agent
resp = httpx.post(f"{RIPLEY_URL}/pay", headers=headers, json={
"destination": "agent-other",
"amount": 0.05,
"memo": "Payment for API call"
}).json()
# Cross-chain: deliver BTC
resp = httpx.post(f"{RIPLEY_URL}/pay", headers=headers, json={
"destination": "bc1q...",
"amount": 0.001,
"currency": "btc"
}).json()
print(f"Track: {resp.get('tracking_url')}")
# Fund account with BTC (deposit swap)
resp = httpx.post(f"{RIPLEY_URL}/deposit/swap", headers=headers, json={
"currency": "btc",
"amount": 0.001
}).json()
print(f"Send {resp['send_amount']} {resp['send_currency']} to {resp['deposit_address']}")
print(f"Expected: ~{resp['xmr_expected']} XMR")
const RIPLEY_URL = "https://pocket.ripley.run";
const headers = { "X-API-KEY": "rp-live-your-key-here", "Content-Type": "application/json" };
// Check balance
const bal = await fetch(`${RIPLEY_URL}/balance`, { headers }).then(r => r.json());
// Internal transfer
const payment = await fetch(`${RIPLEY_URL}/pay`, {
method: "POST",
headers,
body: JSON.stringify({ destination: "agent-other", amount: 0.05 }),
}).then(r => r.json());
// Fund account with ETH
const deposit = await fetch(`${RIPLEY_URL}/deposit/swap`, {
method: "POST",
headers,
body: JSON.stringify({ currency: "eth", amount: 0.01 }),
}).then(r => r.json());
// → Send deposit.send_amount ETH to deposit.deposit_address
import re
def parse_xmr402_challenge(www_auth: str) -> dict:
"""Parse WWW-Authenticate: XMR402 address="...", amount="...", message="...", timestamp="..." """
return dict(re.findall(r'(\w+)="([^"]*)"', www_auth))
def call_with_xmr402(url: str, method: str = "GET", headers: dict = {}, **kwargs) -> httpx.Response:
"""Hit a URL; if it returns 402 with XMR402 challenge, pay via Ripley Pocket and retry."""
resp = httpx.request(method, url, headers=headers, **kwargs)
if resp.status_code != 402:
return resp
www_auth = resp.headers.get("WWW-Authenticate", "")
if not www_auth.startswith("XMR402"):
return resp # Not an XMR402 challenge
# Parse the challenge
challenge = parse_xmr402_challenge(www_auth)
address = challenge["address"]
amount = int(challenge["amount"]) / 1e12 # piconeros → XMR
message = challenge["message"]
# Pay via Ripley Pocket (synchronous — returns txid + proof)
pay_resp = httpx.post(f"{RIPLEY_URL}/xmr402/pay", headers=PAY_HEADERS, json={
"address": address,
"amount": amount,
"message": message,
}).json()
if pay_resp.get("status") == "paid_no_proof":
# Payment sent but proof failed — recover it
import time
time.sleep(5) # Wait for TX propagation
proof_resp = httpx.post(f"{RIPLEY_URL}/xmr402/proof", headers=PAY_HEADERS, json={
"txid": pay_resp["txid"],
"address": address,
"message": message,
}).json()
if proof_resp.get("status") != "proof_recovered":
raise Exception(f"Proof recovery failed: {proof_resp}")
pay_resp = proof_resp # Use recovered proof
elif pay_resp.get("status") != "paid":
raise Exception(f"XMR402 payment failed: {pay_resp}")
# Retry with the cryptographic proof
retry_headers = {**headers, "Authorization": pay_resp["authorization"]}
return httpx.request(method, url, headers=retry_headers, **kwargs)
共 1 个版本