Quickstart
Pick a language; the same five operations show up in every tab. If you want to drop HTTP in anywhere, the curl tab at the bottom of each block is the protocol-level reference.
1. Install
bash
pip install stoka-agentbash
go get github.com/ajbeach2/stoka-gobash
npm install stokabash
# Same package, same install — TypeScript and plain Node share it.
npm install stokabash
# Nothing to install. curl + jq + a signed X-PAYMENT header is enough.2. Get a funded testnet wallet
You need a Stellar testnet seed (S…) with USDC and the USDC trustline. The quickest path is the Circle faucet, which gives you both:
- Generate a wallet (Freighter, the SDK, or the
make wallet-testnettarget in this repo). - Open https://faucet.circle.com/, pick Stellar Testnet, paste the
G…address. - Export the seed:
bash
export STELLAR_SECRET=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX3. Construct the client
python
import os
from stoka_agent import X402Client
client = X402Client(env="test", secret=os.environ["STELLAR_SECRET"])go
package main
import (
"context"
"os"
stoka "github.com/ajbeach2/stoka-go"
)
func main() {
c, err := stoka.New(stoka.Options{
Env: stoka.Testnet,
Secret: os.Getenv("STELLAR_SECRET"),
})
if err != nil {
panic(err)
}
defer c.Close()
_ = context.Background()
}ts
import { createClient } from "stoka";
const client = createClient({
env: "test",
secret: process.env.STELLAR_SECRET!,
});ts
// Same module; Node just pulls the Node-specific signer under the hood.
import { createClient } from "stoka";
const client = createClient({
env: "test",
secret: process.env.STELLAR_SECRET!,
});bash
# No client object for raw HTTP. Set these once and the rest of the
# page refers to them.
export STOKA_API=https://test.api.stoka.space4. Store a blob
python
client.store("greeting", b"remember this tomorrow", ttl_seconds=7 * 86400)go
ctx := context.Background()
_, err := c.Store(ctx, "greeting", []byte("remember this tomorrow"), &stoka.StoreOptions{
TTLSeconds: 7 * 86400,
})ts
await client.store(
"greeting",
new TextEncoder().encode("remember this tomorrow"),
{ ttlSeconds: 7 * 86400 },
);ts
import { readFileSync } from "node:fs";
await client.store("report.pdf", readFileSync("./report.pdf"), {
ttlSeconds: 7 * 86400,
});bash
# The paid flow is a two-request dance: the first call comes back 402
# with payment requirements, the second resends the same request with
# a signed X-PAYMENT header. The clients do this for you; curl cannot.
# See /docs/guide/x402 for the wire format if you want to build your own.
curl -i -X POST "$STOKA_API/v1/store" \
-H "X-Stoka-Key: greeting" \
-H "X-Stoka-TTL-Seconds: 604800" \
-H "Content-Type: application/octet-stream" \
--data-binary "remember this tomorrow"
# → HTTP/1.1 402 Payment Required
# → { "x402Version": 2, "accepts": [ { "maxAmountRequired": "...", ... } ] }5. Retrieve a blob
python
body = client.retrieve("greeting")
print(body) # bytes, auto-decryptedgo
body, err := c.Retrieve(ctx, "greeting", nil)
if err != nil {
panic(err)
}
fmt.Println(string(body))ts
const res = await client.retrieve("greeting");
console.log(new TextDecoder().decode(res.bytes));ts
import { writeFileSync } from "node:fs";
const { bytes } = await client.retrieve("report.pdf");
writeFileSync("./out.pdf", bytes);bash
curl -i "$STOKA_API/v1/retrieve/greeting" \
-H "X-Stoka-Owner: G..." # optional: pins the 402 price6. Update (overwrite)
python
client.update("greeting", b"newer bytes")go
if _, err := c.Update(ctx, "greeting", []byte("newer bytes"), nil); err != nil {
panic(err)
}ts
await client.update("greeting", new TextEncoder().encode("newer bytes"));ts
await client.update("greeting", new TextEncoder().encode("newer bytes"));bash
curl -i -X PUT "$STOKA_API/v1/object/greeting" \
-H "Content-Type: application/octet-stream" \
--data-binary "newer bytes"
# Same 402 → sign → retry flow as store.7. Delete (free, wallet-signed)
python
from stoka_agent import IdentityClient
IdentityClient(env="test", secret=os.environ["STELLAR_SECRET"]).delete("greeting")go
// Delete is not yet wired into the Go client; use DELETE /v1/object/<k>
// directly with an Authorization: Stellar <addr>:<cid>:<sig_hex> header,
// or use the Python/TS client.ts
await client.delete("greeting");ts
await client.delete("greeting");bash
# 1. Ask for a challenge.
curl -s -X POST "$STOKA_API/v1/auth/challenge" \
-H "Content-Type: application/json" \
-d '{"audience":"object-delete"}'
# 2. Sign the `message` with your wallet's Ed25519 key (hex sig).
# 3. Send the delete.
curl -i -X DELETE "$STOKA_API/v1/object/greeting" \
-H "Authorization: Stellar GABC...:<challenge_id>:<hex_signature>"8. Handle payment errors
The clients throw a typed PaymentRequired when they hit a 402 they can't satisfy (no signer, unfunded wallet, missing trustline, unknown asset). Everything else lands as StokaError (or the language's standard HTTP-error equivalent).
python
from stoka_agent import PaymentRequired, StokaError
try:
client.store("greeting", b"...")
except PaymentRequired as e:
# e.requirements carries price / asset / pay_to.
print("fund", e.requirements.pay_to, "with", e.requirements.max_amount_required)
except StokaError as e:
print("HTTP", e.status, e.body)go
import "errors"
var (
pr *stoka.PaymentRequired
se *stoka.StokaError
)
switch {
case errors.As(err, &pr):
fmt.Printf("fund %s with %s %s\n",
c.PublicKey(), pr.Requirements.MaxAmountRequired, pr.Requirements.Asset)
case errors.As(err, &se):
fmt.Printf("HTTP %d: %s\n", se.Status, se.Body)
}ts
import { StokaError } from "stoka";
try {
await client.store("greeting", new Uint8Array([0x68, 0x69]));
} catch (e) {
if (e instanceof StokaError) {
console.error("HTTP", e.status, e.body);
} else {
console.error(e); // Soroban / network
}
}ts
import { StokaError } from "stoka";
try {
await client.store("greeting", new Uint8Array([0x68, 0x69]));
} catch (e) {
if (e instanceof StokaError) {
console.error("HTTP", e.status, e.body);
} else {
console.error(e);
}
}bash
# Non-2xx responses return { "error": "…" } as JSON. A second 402 after
# you already sent X-PAYMENT means the payment couldn't settle — the
# body describes why (insufficient funds, missing trustline, etc).Where next?
- The x402 protocol — what happens between step 4's first request and its retry.
- HTTP API reference — every route, header, and status.
- Python client, Go client, TypeScript client — the per-language deep dives.
- Try it in the browser — signed paid calls with your testnet wallet, no install required.