Storage
Backup blobs for agents, billed by the kilobyte for up to 90 days, read back by signing with the same wallet.
Livefrom $0.002 / MB / 90 daysAccepted assets: USDC, EURQ, USDQ, goBTC
Routes and prices
| Route | What you get | Price |
|---|---|---|
POST / | Free (JSON body: wallet, plus the scope of the call it authorizes: action list / read / delete, backup_id for read and delete, optional version): mint a short-TTL, single-use nonce whose signing_message names that action, backup and site, proving control for exactly one following wallet-signature-authenticated GET/DELETE /storage/backups call (query params: wallet, nonce, signature_b64, optional proof_method). A delete needs a delete-scoped challenge. No session is ever created — sign the returned signing_message and present it fresh on the next call. | free |
POST / | Free (JSON body: wallet, plus the scope of the call it authorizes: action list / read / delete, backup_id for read and delete, optional version): mint a short-TTL, single-use nonce whose signing_message names that action, backup and site, proving control for exactly one following wallet-signature-authenticated GET/DELETE /storage/backups call (query params: wallet, nonce, signature_b64, optional proof_method). A delete needs a delete-scoped challenge. No session is ever created — sign the returned signing_message and present it fresh on the next call. | free |
POST / | Store one opaque backup blob (JSON body: base64 data, optional label; query param declared_size_bytes=<N>, capped at 10 MB, billed by the KB actually used, never rounded up to a whole MB; optional query param retention_days=<1-90>, default 90), priced at max($0.001, ceil(N/1KB) * $0.000001953125 per KB * retention_days/90). Overall remaining life is still capped at 90 days from now. Retrieve or delete it later by proving control of this same wallet again — no session. Stored content is OPAQUE: never scanned, indexed, or acted on by us. Not confidential from us, though — an operator can inspect or remove a specific backup for abuse/legal response (no other agent can read or list your backups). Encrypt sensitive data yourself before uploading if that matters to you. Need more than one version? The versions sub-route below adds a new version under this same backup_id without losing the old ones.
| $0.002 / MB / 90 days |
GET / | Free, wallet-signature-authenticated (query params: wallet, nonce, signature_b64, optional proof_method, from the challenge above; optional query param limit): this wallet's own active, unexpired backups, newest first.
| free |
GET / | Free, wallet-signature-authenticated, owner-only: one backup's metadata (including current_version) plus its CURRENT version's restored bytes (base64), sha256-verified against the hash taken at upload time. See the versions sub-routes below to fetch an older version instead.
| free |
POST / | Extend an existing backup's retrieval window by one full 90-day term, capped at 90 days remaining from now (no request body; query param wallet=<the owning address>, a lookup hint only, never proof of ownership), priced from the backup's already-stored size at max($0.001, ceil(size/1KB) * $0.000001953125 per KB) — always a full term, never a caller-chosen shorter retention (that is create()/add-version()'s own parameter, not this route's). Works during the 2-day grace window after expiry. A backup already at the remaining-term ceiling is refused before payment. Only the wallet that created this backup may renew it: a payment from any other wallet settles but is refused and changes nothing.
| $0.002 / MB / 90 days |
POST / | Store a NEW version's bytes under an EXISTING backup_id without discarding the versions already stored under it (query param declared_size_bytes=<N>, billed by the KB actually used; optional query param retention_days=<1-90>, default 90, priced identically to the initial store: max($0.001, ceil(N/1KB) * $0.000001953125 per KB * retention_days/90); JSON body: base64 data, optional label; wallet as a query param, same lookup-hint shape as renew above). This version becomes the backup's current content, on its own independent expiry sized by its own retention_days — older versions keep expiring on their own original schedule, and the backup's own overall retrieval window (capped at 90 days) is untouched by this call. Only the wallet that created this backup may add a version to it: a payment from any other wallet settles but is refused and changes nothing.
| $0.002 / MB / 90 days |
GET / | Free, wallet-signature-authenticated, owner-only (optional query param limit): metadata (version number, size, content_hash, created_at/expires_at) for every version of one backup, newest-version first.
| free |
GET / | Free, wallet-signature-authenticated, owner-only: one specific version's metadata plus its restored bytes (base64), sha256-verified.
| free |
DELETE / | Free, wallet-signature-authenticated, owner-only: delete one backup outright, every version included (row marked deleted, then connector bytes removed).
| free |
How paying works
- AskCall the route with no payment. It answers 402, and the PAYMENT-REQUIRED header carries the offer: amount, asset, network and the address to pay.
- SignSign an Algorand asset transfer for exactly that offer with your wallet. Nothing reaches the chain yet.
- RepeatRepeat the same call with the signed group in PAYMENT-SIGNATURE. The facilitator settles it and the response carries the result and the transaction id.
Call it
With curl you handle the 402, the signature and the retry yourself; the pxke_x402 Python client does all three inside one call.
# Store: declare the size so the price is fixed before the body is read
DATA=$(base64 -w0 state.json); SIZE=$(stat -c%s state.json)
curl -si -X POST "https://algorand-api.pxke.me/api/v1/x402/storage/backups?declared_size_bytes=$SIZE&retention_days=90" \
-H 'Content-Type: application/json' \
-d "{\"data\": \"$DATA\", \"label\": \"my-agent-state-backup\"}"
# -> 402 with the offer; sign it, resend the same request with PAYMENT-SIGNATURE
# Read back (free): ask for a nonce, sign it with the same wallet, present it once
curl -s -X POST 'https://algorand-api.pxke.me/api/v1/x402/storage/auth/challenge' \
-H 'Content-Type: application/json' -d '{"wallet": "'$WALLET'"}'
curl -s "https://algorand-api.pxke.me/api/v1/x402/storage/backups/$BACKUP_ID?wallet=$WALLET&nonce=$NONCE&proof_method=signed_bytes&signature_b64=$SIG"
from pxke_x402 import PxkeClient
client = PxkeClient(mnemonic=MNEMONIC)
with open("state.json", "rb") as f:
blob = f.read() # encrypt first if it is sensitive
backup = client.storage_create_backup(blob, label="my-agent-state-backup")
print(backup["backup_id"], backup["expires_at_epoch"])
# Later: add a version under the same id, or extend the retrieval window
client.storage_renew_backup(backup["backup_id"], wallet=client.address)
What comes back
A successful POST /api/v1/x402/storage/backups answers 200 with this JSON. settlement_tx_id is the Algorand transaction that paid for it.
{
"backup_id": "7f3a9c2e-5b1d-4e8f-9a6c-2d4b8e1f0a3c",
"size_bytes": 24576,
"content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"label": "my-agent-state-backup",
"created_at_epoch": 1788968366,
"expires_at_epoch": 1796744366,
"status": "active",
"settlement_tx_id": "JQ3G…R5YA",
"current_version": 1
}Limits and notes
- One blob per backup, up to 10 MB, sent as base64 in the JSON body. Declare its size in declared_size_bytes so the price is fixed before the body is read.
- Priced per KB actually used, for 1 to 90 days of retention, with a small floor per write. Renewing adds a full 90-day term, capped at 90 days remaining.
- Reads, listings and deletes are free but need a fresh signature each time: request a challenge, sign it with the same wallet, present it once.
- Content is opaque to us and never scanned or indexed, but not confidential from an operator. Encrypt anything sensitive before uploading.