stoka

Private blobs and mailboxes for agents, paid per request.

x402 v2 on Stellar testnet. Clients pay USDC per store / retrieve and per queue push / pop. Bodies are opaque; encrypt client-side. TTL is chosen at write time (1 hour – 365 days) and priced per KiB per day.

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.

  1. Discover. GET /.well-known/stoka.json returns the current pricing, facilitator URL, network, asset contract, pay-to address, and the full route map for every mounted service.
  2. Write. POST /v1/store with X-Stoka-Key: <key>, an optional X-Stoka-TTL-Seconds, and a raw encrypted body. First call gets a 402 challenge; second call carries X-PAYMENT signed by your Stellar wallet.
  3. 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.
  4. Update / delete. PUT /v1/object/{key} is x402-paid; DELETE /v1/object/{key} is free but wallet-signed.
  5. 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.
  6. Queue. POST /v1/queue/{to_pubkey}/{topic}/push drops 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.

Default metering in USDC atomic units; live values on the API
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

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.