Built on x402 · Powered by Base

Your agent shouldn't need
your credit card.

One endpoint. One payment. No accounts. No recurring billing. Files expire automatically — down to the second.

Get started → View pricing API

Think of it like a parking meter. You pay for a spot, for a defined time, then it expires automatically. Wallet = identity. No signup required. Store it. Pay for it. Done.

How it works

From task to storage
in a single HTTP call.

Discover and quote are optional. If you already know the rate and wallet address, skip straight to the upload — one POST and you're done.

TL;DR — minimum viable agent call

POST to /api/store with an x402 payment header, your file as the body, and three headers: X-File-Name, X-Size-Bytes, X-Duration-Seconds. Receipt comes back in the response.

Optional

Discover

Hit /api/pricing once to get the rate and wallet address. Cache it — it rarely changes.

Optional

Quote

Call /api/quote to confirm exact price. Or calculate yourself: (bytes/1GB) × (seconds/86400) × $0.001.

Required

Pay & Upload

One POST with x402 payment header and file body. Everything happens in a single atomic request.

Done

Spin down

Write the lease_id and expires_at. File physically deletes itself when the lease expires — to the second.

Quickstart

One required step.
Two optional ones.

No SDK. No IAM. No billing account. Just HTTP.

Agent flow — 1 required step
// ── OPTIONAL: discover pricing once and cache it ─────────────────────────
const pricing = await fetch('https://stashdata.dev/api/pricing').then(r => r.json());
// → { rateUsdcPerGbDay: 0.001, walletAddress: '0x...', minDurationSeconds: 60 }

// ── OPTIONAL: get an exact quote ─────────────────────────────────────────
const quote = await fetch(
  `https://stashdata.dev/api/quote?size_bytes=${fileBytes}&duration_seconds=3600`
).then(r => r.json());
// → { priceUsdc: 0.01, expiresAt: '2026-04-16T17:00:00Z' }

// ── REQUIRED: pay and upload in one request ───────────────────────────────
const receipt = await fetch('https://stashdata.dev/api/store', {
  method: 'POST',
  headers: {
    'X-Payment':         buildX402Header(quote.priceUsdc, pricing.walletAddress),
    'X-File-Name':       'report.pdf',
    'X-Size-Bytes':      String(fileBytes),
    'X-Duration-Seconds': '3600',  // 1 hour — be precise, don't over-specify
    'Content-Type':      'application/pdf',
  },
  body: fileStream,
});
// → { lease_id, object_key, expires_at, duration_human: '1 hour', paid_usdc }
Why Stashdata

S3 can't take a crypto payment
from an agent mid-task.

You're not cheaper than S3. You're categorically different from S3.

AWS S3 / R2 / Backblaze

Requires IAM setup by a human
Monthly billing to a credit card
Agent can't open an account mid-task
No second-level expiry — lifecycle rules are day-granularity only
No agent-to-agent handoff primitive

Stashdata

No account, no IAM, no signup
Pay once per object, upfront in USDC
Agent pays autonomously mid-task
Expiry to the second — physical deletion enforced
Sealed handoff tokens for agent-to-agent transfer
Agent-to-agent handoff

Pass a file to another agent.
One claim. Then it's gone.

Once you've stored a file, you can issue a sealed handoff token — a one-time (or N-time) claim URL you pass to another agent. No auth required to claim. The token IS the credential.

How handoff works

Think of it as a self-destructing sealed envelope. Agent A stores a file and seals it. Agent B receives the claim URL and opens it. The envelope is gone. No shared backend, no trust required between agents.

Agent A — store

Upload file, get lease_id back in receipt.

Agent A — seal

POST to /api/handoff/{lease_id} with max_claims and optional delete_on_claim. Get back a claim_url.

Agent A → B

Pass the claim_url to Agent B through any channel — message queue, shared context, webhook.

Agent B — claim

GET the claim_url — no auth, no wallet, no setup. File streams back. If delete_on_claim, it's physically deleted immediately after.

Handoff flow
// Agent A — after uploading, issue a handoff token
const handoff = await fetch(`https://stashdata.dev/api/handoff/${lease_id}`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${jwt}` },
  body: JSON.stringify({
    max_claims:      1,      // single use
    delete_on_claim: true,  // physically deleted after Agent B claims it
    ttl_seconds:     3600,  // token expires in 1 hour regardless
  }),
}).then(r => r.json());
// → { claim_url: 'https://stashdata.dev/api/claim/abc123...', expires_at }

// Pass claim_url to Agent B through any channel
await messageQueue.send({ claim_url: handoff.claim_url });

// Agent B — claim the file, no auth required
const file = await fetch(handoff.claim_url);
// → file stream. Token is now exhausted. File deleted if delete_on_claim was true.
Token options:   max_claims — how many times the token can be used (default 1)  ·  delete_on_claim — physically delete from S3 after final claim (default false — owner keeps access)  ·  ttl_seconds — token expiry independent of lease expiry (default 3600)
Pricing

Pay for exactly what you need.
Nothing more.

Rate: $0.001 / GB / day. Duration in seconds — specify exactly how long you need it. Many small transactions hit the network minimum, shown in green.

Duration is a tool, not just a limit. Your file is physically deleted at the end of the period you specify — not before, not after. If you pay the minimum charge for a 1 MB PDF, request exactly how long you need it: 300 seconds, 1 hour, 1 day. Don't over-specify — a file requested for 10 years will stay for 10 years even if your agent only needed it for an hour.
1 MB PDF contract
4 GB video file

Custom calculator

for
Built for agents

Spin up. Work. Store.
Spin down.

Every design decision optimises for autonomous operation with zero human involvement.

🔍

Machine-discoverable

Agents find Stashdata via /.well-known/x402.json or /api/pricing. No documentation required.

💳

Wallet = identity

No signup, no API key, no OAuth. The payment itself proves identity. Your wallet is your account.

⏱️

Expiry to the second

Duration is specified in seconds. Files are physically deleted the moment the lease expires — not day-granularity, not best-effort.

🤝

Agent-to-agent handoff

Issue a sealed claim token from any lease. Another agent claims the file with no auth — the token IS the credential. Single-use or N-use, with optional delete-on-claim.

📦

No size ceiling

Files stream directly to S3 via multipart upload. 1KB or 4GB — same endpoint, flat memory.

🔒

S3-grade reliability

Built on AWS S3. 99.999999999% durability. No blockchain storage, no probabilistic guarantees.

API Reference

Five endpoints.
No SDK required.

All endpoints return JSON. The store endpoint accepts a raw file body stream.


POST
Required
/api/store
Upload a file with x402 payment. Send file bytes as body. Required headers: X-Payment, X-File-Name, X-Size-Bytes, X-Duration-Seconds (min 60). Returns lease_id, object_key, expires_at, duration_human.
POST
New — Handoff
/api/handoff/{lease_id}
Issue a claim token for a lease you own. JWT required. Body: max_claims (default 1), delete_on_claim (default false), ttl_seconds (default 3600). Returns claim_url, handoff_token, expires_at.
GET
New — Claim
/api/claim/{token}
Claim a file using a handoff token. No auth required — the token IS the credential. Returns file stream. Enforces max_claims and deletes from S3 if delete_on_claim is true.
GET
Optional
/api/pricing
Machine-readable service discovery. Returns rate, wallet address, payment config, and worked examples. Agents parse this once and cache it. Also available at /.well-known/x402.json.
GET
Optional
/api/quote?size_bytes={n}&duration_seconds={n}
Returns exact price in USDC. Stateless — no reservation, no lock. Or calculate yourself: (bytes/1073741824) × (seconds/86400) × 0.001.
GET
/api/leases
List all files stored by your wallet. Requires JWT — get one via /api/auth/challenge + /api/auth/verify. Or use the dashboard at /dashboard.html.
GET
/api/download/{lease_id}
Stream a stored file back to the owner. Requires JWT. Verifies wallet ownership. Returns 410 Gone if lease has expired — file is physically deleted on first access attempt after expiry.