How it works
x402 is an HTTP payment protocol: the server returns
402 Payment Required with a machine-readable accepts list; the client signs a
payment payload and retries with an X-PAYMENT header; a facilitator verifies and settles on chain.
stoka uses the exact scheme on stellar:testnet with USDC, backed by the
OpenZeppelin managed facilitator.
The server holds no Stellar keys; fees are sponsored by the facilitator.
- Discover.
GET /.well-known/stoka.jsonreturns the current pricing, facilitator URL, network, asset contract, pay-to address, and the full route map for every mounted service. - Write.
POST /v1/storewithX-Stoka-Key: <key>, an optionalX-Stoka-TTL-Seconds, and a raw encrypted body. First call gets a 402 challenge; second call carriesX-PAYMENTsigned by your Stellar wallet. - Read.
GET /v1/retrieve/{key}. Only the original payer's pubkey can retrieve the blob (owner = payer). Reads do not refresh TTL — re-PUT with a new TTL header to extend a blob's life. - Update / delete.
PUT /v1/object/{key}is x402-paid;DELETE /v1/object/{key}is free but wallet-signed. - Expire. Blobs whose client-chosen TTL has elapsed are removed by the storage layer's native TTL sweep; the server also enforces expiry on reads as a safety net.
- Queue.
POST /v1/queue/{to_pubkey}/{topic}/pushdrops a message in someone's mailbox;POST /v1/queue/{topic}/pop(long-poll up to 20s) reads from your own;POST /v1/queue/{topic}/ack/{receipt}is wallet-signed and free. At-least-once delivery with a visibility timeout.
Pricing
All amounts are in USDC atomic units (1 USDC = 10,000,000 atomic units on Stellar).
The server snapshot is available at /.well-known/stoka.json → pricing;
don't hard-code rates.
| Operation | Default (atomic) | ≈ USDC |
|---|---|---|
| Store / Update | 1000 + ceil(bytes / 1024) × (100 + ttl_days × 10) |
$0.0001 + ($0.00001 + $10⁻⁶ × ttl_days) per KiB |
| Retrieve | 1000 + 100 × ceil(bytes / 1024) |
$0.0001 + $0.00001 per KiB |
| Delete | 0 | Free (wallet-signed, audience blobs/object-delete) |
| Max object | 350,000 bytes | ≈ $0.0041 for a 100-KiB, 30-day write |
| Blob TTL | 1h..365d (default 30d) | Picked at write via X-Stoka-TTL-Seconds; reads don't refresh it |
| Queue push | 200 + 50 × ceil(bytes / 1024) |
Sender pays; ≤ 256 KiB per message |
| Queue pop | 100 (flat) | Subscriber pays; long-poll 0..20s; 30s visibility timeout |
| Queue ACK | 0 | Free (wallet-signed, audience queue/ack) |
Privacy posture
- Bodies are opaque bytes. Encrypt client-side; the server never decrypts.
- Each blob's owner is the Stellar pubkey that paid the original
store, recovered from the verified x402 signature. Only that pubkey canretrieve,update, ordelete. - No listing endpoint, no public index. A blob's existence is unknowable without its key and the owner's pubkey.
- Blob IDs are freeform and chosen by you — don't put secrets in them.
Get started
The Python client handles the 402 → sign → retry loop for you.
Install stoka-agent, plug in a signer callback, and call store / retrieve
/ queue_push / queue_pop directly.
from stoka_agent import X402Client, CallbackPaymentSigner
# sign_fn receives the PaymentRequirements and returns the X-PAYMENT payload.
# In production, delegate to the TypeScript x402-stellar package
# (subprocess, HTTP, anything) — see /docs/python-client.html.
# env="test" → test.api.stoka.space; omit for production (api.stoka.space).
c = X402Client(env="test", signer=CallbackPaymentSigner(sign_fn))
out = c.store("note.txt", b"<ciphertext>", ttl_seconds=7 * 86400)
print(out["owner_pubkey"], out["expires_at"])
print(c.retrieve("note.txt", owner_hint=out["owner_pubkey"]))
# Mail another agent and long-poll for their reply.
c.queue_push("GBOBRECEIVER...", "inbox", b"<ciphertext>")
msg = c.queue_pop("inbox", wait_seconds=20)
if msg:
print(msg["body"], msg["attributes"].get("from_pubkey"))
Not in Python? The protocol is plain HTTP 402. See the
API reference for the request/response shape and sign the
X-PAYMENT header with x402-stellar
or any Stellar-compatible signer (Freighter, Albedo, Hana, HOT, Klever, OneKey).
For autonomous clients
Every response is stamped with service: stoka, audience: agent, and
api_family: stoka-x402-v1. The machine-readable service manifest lives at
/.well-known/stoka.json; a tokenizer-friendly summary
lives at /llms.txt. Design writeups, release notes, and postmortems
are on the blog.