# Nukez — LLM Full Index (Walkthrough Edition) Nukez is an HTTP-first storage service for autonomous agents. Purchases use the x402 payment protocol (HTTP 402 Payment Required carrying machine-readable payment instructions) across Solana and EVM (Monad). Every payment produces a cryptographically signed receipt. There are no accounts or API keys — a blockchain wallet is the only credential, and per-request authorization is a signed envelope committed to the wallet key. - Base URL: https://api.nukez.xyz - Docs: https://docs.nukez.xyz - Discovery: https://docs.nukez.xyz/.well-known/nukez.json - OpenAPI: https://docs.nukez.xyz/openapi.json - Tool schema: https://docs.nukez.xyz/v1/tools.json - Tool contract: https://docs.nukez.xyz/v1/tool_contract.json - Signed-envelope auth: https://docs.nukez.xyz/docs/AUTH_SIGNED_ENVELOPE.md - Agent flow: https://docs.nukez.xyz/docs/AGENT_FLOW.md - Merkle spec: https://docs.nukez.xyz/docs/verify/merkle-v1.md - Error recovery: https://docs.nukez.xyz/docs/ERROR_RECOVERY.md - Short index: https://docs.nukez.xyz/llms.txt # Core Storage Walkthrough Lifecycle: quote → pay on-chain → confirm → provision locker → create file entry → upload bytes → confirm upload → download. Every stage is idempotent under retry, and every artifact is deterministic from prior artifacts: an agent holding only `receipt_id` can reconstruct locker identity, file listing, and content proofs. ## 1. Quote — `POST /v1/storage/request` Optional JSON body: `units` (integer 1–100, default 1), `provider` (`gcs`, `s3`, `storj`, `mongodb`, `arweave`, `filecoin`, `firestore` — enabled subset advertised by the gateway), `pay_network` (CAIP-2 or friendly name like `solana-mainnet`, `monad-mainnet`), `pay_asset` (`SOL`, `MON`, `USDC`, `USDT`, `USDT0`, `WETH`, `BETA`). Sandbox-only: `execution_mode`, `existing_receipt_id`, `explicit_purchase`. An `Idempotency-Key` (also `X-Idempotency-Key`) header makes retries safe; reusing with a different body returns HTTP 409. Response is **always HTTP 402 Payment Required** — the x402 challenge, not an error. Body is an x402 v2 envelope with top-level keys `x402Version` (integer `2`), `resource` (`url`, `description`, `mimeType`), `accepts` (one entry per chain/asset leg), `error`, and `extensions.nukez` carrying `terms` (including `terms_hash`), `instructions`, and `price_summary` (`unit_price_usd`, `units`, `total_usd`, `provider`). The same envelope is placed as compact base64 in the `PAYMENT-REQUIRED` response header. Each `accepts[]` entry has `scheme` (always `"exact"`), `network` (CAIP-2), `amount` (raw atomic units as string — lamports for SOL, wei for EVM native, base units for tokens), `asset` (token mint or contract; native EVM = zero address `0x0000000000000000000000000000000000000000`; native SOL = wrapped-SOL mint), `payTo`, `maxTimeoutSeconds`, and an `extra` object with `name`, `decimals`, `human_amount`, `pay_req_id`, `idempotency_key`, `quote_expires_at`, and `quote_schema` (`"nukez_quote_v4"`). When present, `extra` also carries `oracle_rate`, `price_observation` (with `provider` of `pyth` on Solana or `chainlink` on Monad, `feed`, `price`, `observed_at`, `retrieved_at`, `cross_check`), `destination_kind` (`"wallet"`, `"spl_token_account"`, `"evm_address"`), and — only when `destination_kind == "spl_token_account"` — `spl_transfer` with `program_id`, `destination_token_account`, `mint`, `amount_raw`, `decimals`. Fields absent on the wire must not be assumed present. Stablecoin legs are priced at parity and carry no `price_observation`; when a live feed is unavailable or fails cross-check, that asset is omitted from `accepts[]`. For Solana SPL token payments, `payTo` is the treasury's SPL token account. Pass it directly as the `transferChecked` destination — **do not derive an associated token account**. Persist `extra.pay_req_id`; required at confirm. For a non-binding pricing preview, read `extensions.nukez.price_summary` from the 402 body, or call unauthenticated `GET /v1/price?units=N`. That response has top-level `unit_price_usd` plus `meta.total_usd`, `meta.payment_options`, and a SOL breakdown at `meta.sol.amount_sol` / `meta.sol.amount_lamports`. Do not hardcode amounts. ## 2. Pay on-chain The SDK does not execute payments. Using the leg you selected, transfer the exact `amount` (raw atomic units) to `payTo` on the indicated `network`. Native SOL: system transfer of lamports. Solana SPL: `transferChecked` with the exact destination in `payTo`. EVM native: value transfer. ERC-20: `transfer`. Capture the tx signature: Solana signatures are base58; EVM tx hashes are `0x`-prefixed hex. ## 3. Confirm — `POST /v1/storage/confirm` Send tx proof in the `X402-TX` request header (also `X-402-TX`). Fallback for constrained clients: `tx_sig` in body. Body must include `pay_req_id`. When paying any non-default leg of `accepts[]`, body must include `pay_asset` and `pay_network` matching the leg paid (aliases `payment_asset`, `payment_chain` accepted). The gateway falls back to the quote's default leg only when both are omitted; mismatch returns HTTP 400 `" on not in quote"`. To bind a delegated signing key at confirm time, include `operator_pubkey` and `payer_signature`. `payer_signature` is Ed25519 base58 for Solana or an EIP-191 personal_sign `0x`-hex signature for EVM, computed over the exact UTF-8 bytes `nukez/confirm/v1|||` (single hyphen in place of the operator when none). Confirm-intent enforcement rolls out in stages via `CONFIRM_INTENT_MODE`: once enforced, unsigned intents return 401 `CONFIRM_INTENT_REQUIRED` or `CONFIRM_INTENT_INVALID`, and the 401 detail carries `sign_this` with the exact text to sign. **Confirm does not use a signed envelope** — `X402-TX` is the payment proof. Signature format: Solana signatures in `X402-TX` are base58, 64–128 chars; EVM tx hashes are `0x` + exactly 64 hex chars (66 total). Success (HTTP 200): ``` { "ok": true, "receipt_id": "<16-hex>", "receipt": { ... }, "payer_pubkey": "...", "tx_sig": "...", "sig_alg": "ed25519" | "secp256k1", "authorized_operator": "..." // omitted when none } ``` If `pay_req_id` was already confirmed with the same `tx_sig`, same receipt returns with `"idempotent": true`. Different `tx_sig` against same `pay_req_id` returns HTTP 409. Immutable receipt fields: `id` (also `receipt_id`), `resource` (always `"storage"`), `pay_req_id`, `tx_hash`, `owner_id` / `payer_pubkey`, `pay_to_address`, `network` (CAIP-2), `pay_asset`, `units`, `unit_price_usd`, `price_usd`, `paid_amount` (human), `paid_raw` (atomic), `slot` (Solana; null on EVM), `block_number` (EVM; null on Solana), `tx_timestamp_unix`, `receipt_sig_alg` / `sig_alg` (`"ed25519"` Solana, `"secp256k1"` EVM), `authorized_operator`, `receipt_sig`, `receipt_hash`, `receipt_signer_pubkey`, `created_at`, `provider`. **Receipt ID derivation.** First 16 hex chars of `SHA256("storage|" + pay_req_id + "|" + tx_sig)`. Retrying confirm with same `pay_req_id` and `tx_sig` always yields the same `receipt_id`, so retries are safe. Quotes expire; receipts do not. Confirming after `extra.quote_expires_at` returns HTTP 410 Gone with `"error": "quote_expired"`, `expired_at`, `expired_ago_sec`. If the tx was broadcast before expiry, a late-confirm grace applies; payments that landed before the quote existed return `PAYMENT_OUTSIDE_QUOTE_WINDOW`. ## 4. Provision locker — `POST /v1/storage/signed_provision` Signed envelope, `ops: ["locker:provision"]`. Body: `{"receipt_id": "...", "tags": [] (optional), "operator_pubkey": "..." (optional)}`. Envelope's `receipt_id` must match body. `POST /v1/storage/provision` accepts the same body and also validates signed headers; without them it only works in explicitly-enabled local dev mode. Response returns a `locker_manifest_v2` under `space` with `locker_id`, `receipt_id`, `provider`, `path_prefix`, `attestation_jwt`, and a `file_api` sub-object listing `create_file`, `list_files`, `get_file`, `delete_file`, `signed_url_default_ttl_min` (default 30). **Locker ID derivation.** `"locker_" + SHA-256(receipt_id).hex()[:12]`. Always locally computable — persisting `receipt_id` alone reconstructs locker identity. ## 5. Create file entry — `POST /v1/lockers/{locker_id}/files` Signed envelope, `ops: ["locker:write"]`. Optional body: `filename` (auto-generated if omitted; must match `[a-zA-Z0-9_][a-zA-Z0-9._/\-]{0,254}`; reserved `.memory/` prefix explicitly allowed for agent-memory files), `content_type` (inferred from filename if omitted), `ttl_min` (URL lifetime, 1–1440, default 30), `expected_hash` (SHA-256 precommit; `sha256:` prefix optional), `expected_size_bytes`, `upload_mode` (`"put"` default; `"resumable"` on providers advertising `supports_resumable_upload`, currently GCS). Response returns `upload_url`, `download_url`, `filename`, `content_type`, `object_key`, `upload_headers` (dict with `Content-Type` the caller must send on PUT), `download_headers`, `urls_expire_in_sec`, and — when the auth envelope carries a `receipt_id` — `confirm_url` and `confirm_required: true`. When `upload_mode: "resumable"` is honored, a `resumable_upload` block is included with URL, headers, `chunk_alignment_bytes`, protocol identifier. Batch create: `POST /v1/lockers/{locker_id}/files/batch` accepts `{"files": [{"filename", "content_type", "expected_hash"}, ...], "ttl_min": 30}`, 1–100 items, returning per-file `upload_url` / `download_url` and a single `confirm_batch_url` when a receipt is bound. **Short URLs (`/f/{token}`) are Ed25519-signed and method-bound.** Signed payload commits to the HTTP method minted for. Upload tokens accept only PUT; download tokens accept only GET (HEAD as metadata-only subset); DELETE tokens accept only DELETE. Any other method returns 403. A leaked download link cannot be replayed as write or delete. ## 6. Upload bytes PUT raw bytes to `upload_url` with `Content-Type` from `upload_headers`. Two paths: - **Proxied PUT (`?proxy=1`).** Bytes stream through the gateway, which computes SHA-256 while forwarding, records `content_hash` and `size_bytes` inline in the manifest, returns `X-Nukez-Content-Hash-Recorded: true` (or `false` on write-back failure). No separate confirm needed on success. - **Direct PUT (no `?proxy=1`).** Signed-URL providers (GCS, S3, Storj) answer with HTTP 307 redirect to a provider signed URL; client follows and PUTs bytes directly to storage. The 307 response carries `X-Nukez-Confirm-Required: true` because the gateway did not observe the bytes. Proxy providers (MongoDB, Firestore) and content-addressed providers (Arweave, Filecoin) always proxy through the gateway. Content-addressed uploads use the sandbox ingest flow: `POST /v1/lockers/{locker_id}/upload-sessions`, append base64 chunks via `POST /v1/upload-sessions/{session_id}/files/{file_id}/parts` (each with `part_no`, `payload_b64`, `is_last`), finalize with `POST /v1/upload-sessions/{session_id}/finalize`. The gateway records `provider_ref` (Arweave tx ID or Filecoin CID). Arweave enforces ~5 MiB per-object cap and 15–60 second post-upload propagation; MongoDB enforces 16 MiB per-doc cap (creating with `expected_size_bytes` above 16 MiB returns HTTP 413). ## 6b. Confirm upload — `POST /v1/files/confirm` Signed envelope, `ops: ["locker:write"]`. Query: `?receipt_id=...&filename=...`. **POST body is literally `{}`**; envelope binds method, path, query, and `body_sha256` of canonical `"{}"`. Envelope's `receipt_id` must equal query `receipt_id` (mismatch returns 401 `RECEIPT_ID_MISMATCH`). Sign the confirm envelope after upload finishes — envelopes with `exp − iat > 300` seconds are rejected, so long uploads must re-sign. Batch confirm: `POST /v1/files/confirm-batch?receipt_id=...&filenames=a&filenames=b` (repeat `filenames`). Returns `{"locker_id", "confirmed", "errors", "results", "error_details"}`. On confirm, the gateway downloads stored bytes, computes SHA-256, writes `content_hash`, `size_bytes`, `hash_recorded_at` to the manifest. If `expected_hash` supplied at create differs from computed hash, response is HTTP 409 `HASH_MISMATCH`. Enforcement is staged (`CONFIRM_AUTH_MODE`): when `enforce`, missing envelope headers on confirm/confirm-batch return 401 `SIGNED_ENVELOPE_REQUIRED`; in `shadow` (default), missing headers are logged but request proceeds. Signed request is the contract regardless. After a confirmed upload (and after delete), the gateway fires a non-blocking automatic re-attestation trigger, skipped when the manifest is unchanged since the last attestation. ## 7. Download Three ways to read bytes: - Signed short URL `download_url` from create or get (expires per `ttl_min`). - Receipt-scoped permanent proxy `GET /v1/r/{receipt_id}/f/{filename}` — no auth. Append `?download=true` to force `Content-Disposition: attachment`. Includes `X-Nukez-Content-Hash` when hash is on record. Signed-URL providers get HTTP 307; document-store providers proxy bytes directly. - Refresh expired short URLs via `GET /v1/lockers/{locker_id}/files/{filename}?ttl_min=30` (signed, `locker:read`) or bulk `POST /v1/lockers/{locker_id}/files/urls` with `{"filenames": [...], "ttl_min": 30}`. Short-URL token signatures can be verified via `GET /v1/short-url/verify-key`, returning `{"verify_key_hex", "algorithm": "ed25519", "usage": "short_url_token_verification"}`. This key signs short-URL tokens only; it is **not** the receipt signer. # File Operations Summary All require signed envelopes with the indicated ops: - `POST /v1/lockers/{locker_id}/files` — `locker:write` - `POST /v1/lockers/{locker_id}/files/batch` — `locker:write` - `GET /v1/lockers/{locker_id}/files` — `locker:list` - `GET /v1/lockers/{locker_id}/files/{filename}` — `locker:read` - `DELETE /v1/lockers/{locker_id}/files/{filename}` — `locker:write` - `GET /v1/lockers/{locker_id}/manifest` — `locker:read` - `GET /v1/lockers/{locker_id}/record` — `locker:read` - `POST /v1/lockers/{locker_id}/files/urls` — `locker:read` - `POST /v1/files/fetch-and-store` — `locker:write`; body `{"source_url": "https://...", "filename": "...", "content_type"?}`; HTTPS-only, gateway-side fetch (up to 100 MiB), SSRF-protected - `POST /v1/lockers/{locker_id}/upload-sessions` — `locker:write` - `POST /v1/lockers/{locker_id}/operators` — `locker:admin` - `DELETE /v1/lockers/{locker_id}/operators/{pubkey}` — `locker:admin` Manifest response: `locker_id`, `schema: "locker_files_v1"`, `files[]` (each: `filename`, `content_type`, `object_key`, `provider`, `created_at`, `updated_at`, `content_hash`, `size_bytes`, `hash_recorded_at`, `expected_hash`, `expected_size_bytes`, `provider_ref`, optional `enrichment`), `file_count`, `total_bytes`, `hashed_file_count`, `created_at`, `updated_at`. `content_hash` and `size_bytes` are absent until a confirm records them, though `size_bytes` may be lazily backfilled from provider metadata on list. Locker record (`GET /v1/lockers/{locker_id}/record`) includes `locker_id`, `receipt_id`, `owner_id`, `provider`, `bucket`, `path_prefix`, `metadata_key`, `operator_ids[]`, `tags`, `created_at` — no file data. Locker plus `receipt_id` fully restores agent context. # Authentication: Signed Envelopes Every authenticated call is authorized by a request-bound envelope; there are no bearer tokens. Headers on every authenticated call: - `X-Nukez-Envelope`: `base64url(canonical_json(envelope))` (no padding). - `X-Nukez-Signature`: `base58(ed25519_signature)` for Solana, or `0x`-prefixed hex EIP-191 personal_sign signature (65 bytes / 132 chars including `0x`) for EVM, computed over the canonical envelope bytes. Envelope fields: `v` (always `1`), `locker_id`, `receipt_id`, `nonce` (random single-use string, ≥ 8 chars, unique per locker), `iat` (Unix seconds; legacy `ts` accepted), `exp` (Unix seconds), `ops` (scope strings), `method` (uppercase), `path`, `query` (URL-encoded, sorted by key), `body_sha256` (lowercase hex SHA-256 of body bytes). Optional: `sig_alg` (`"ed25519"` or `"secp256k1"`, alias `"personal_sign"`) and `signer` (operator pubkey when signing as operator). **Canonical JSON.** `json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")` — sorted keys, compact separators, raw UTF-8 (no `\uXXXX` escapes). Serialize the body to canonical JSON once, hash that byte string for `body_sha256`, and send those exact bytes on the wire. Byte-level mismatch returns 401. **TTL ceiling.** Gateway rejects envelopes where `exp − iat > 300` seconds; ~60 seconds is typical. Clock-skew tolerance ±60 seconds on `iat` and `exp`. **Retry safety.** A processed nonce is stored with a cached response (default 120 seconds). Replaying the same envelope within that window returns the original response verbatim. Outside the window, or if the original handler crashed before caching, replays return HTTP 409 `NONCE_REPLAY`. Server-side 5xx releases the nonce so retries with the same envelope are safe. **Two intentional missing-header behaviors.** - **422 VALIDATION_ERROR** on routes that declare envelope and signature headers as required in the FastAPI signature. Includes `POST /v1/storage/attest` and `POST /v1/attest/push`. - **401 SIGNED_ENVELOPE_REQUIRED** on routes that read the headers optionally and enforce them in the handler. Includes `POST /v1/storage/provision` (when not in local-dev bypass), `POST /v1/storage/signed_provision`, `POST /v1/storage/refresh`, `GET /v1/storage/recompute-verify`, `POST /v1/files/fetch-and-store`, `GET /v1/lockers/{locker_id}/record`, `GET /v1/lockers/{locker_id}/urls`, `GET /v1/storage/metadata/{locker_id}`, all `require_locker_authority` routes under `/v1/lockers/{locker_id}/…` (file, operator, manifest, upload-session, viewer), upload-session part/finalize/viewer endpoints, job endpoints, and — when `CONFIRM_AUTH_MODE=enforce` — confirm and confirm-batch. Present-but-invalid envelopes (bad signature, expired window, body-hash mismatch, receipt-ID mismatch) always fail in the 401 family regardless of route. **Scopes.** - `locker:provision` — `POST /v1/storage/provision`, `POST /v1/storage/signed_provision` - `locker:refresh` — `POST /v1/storage/refresh` - `locker:read` — `GET /v1/lockers/{locker_id}/record`, `GET /v1/storage/metadata/{locker_id}`, `GET /v1/lockers/{locker_id}/files/{filename}`, `GET /v1/lockers/{locker_id}/manifest`, `POST /v1/lockers/{locker_id}/files/urls`, `GET /v1/storage/recompute-verify` - `locker:list` — `GET /v1/lockers/{locker_id}/files` - `locker:write` — `POST /v1/lockers/{locker_id}/files`, `POST /v1/lockers/{locker_id}/files/batch`, `DELETE /v1/lockers/{locker_id}/files/{filename}`, `POST /v1/files/confirm`, `POST /v1/files/confirm-batch`, `POST /v1/files/fetch-and-store`, `POST /v1/lockers/{locker_id}/upload-sessions` - `locker:urls` — `GET /v1/lockers/{locker_id}/urls` - `locker:attest` — `POST /v1/storage/attest`, `POST /v1/attest/push` - `locker:admin` — `POST /v1/lockers/{locker_id}/operators`, `DELETE /v1/lockers/{locker_id}/operators/{pubkey}` # Operator Delegation Operators are additional signing keys the locker owner authorizes for file operations. Register at confirm (`operator_pubkey` in confirm body with `payer_signature`), at provision (`operator_pubkey` in `signed_provision`), or later via `POST /v1/lockers/{locker_id}/operators` with `{"pubkey": "..."}`. Remove via `DELETE /v1/lockers/{locker_id}/operators/{pubkey}`. Add and remove both require the owner to sign a `locker:admin` envelope. Max 5 operators per locker; operator key must differ from owner's. Operators sign file envelopes by setting `signer` to their own pubkey (and `sig_alg` matching their key type). The gateway loads the locker record, confirms `signer` appears in `operator_ids`, and verifies against the operator's key. Operators cannot manage other operators — only the owner holds `locker:admin`. An unauthorized operator is rejected with `NOT_AUTHORIZED_OPERATOR`. **Cross-chain delegation.** An EVM (secp256k1) owner can register an Ed25519 Solana pubkey as operator (via confirm-time binding), so subsequent file ops use the Solana scheme. Symmetric pattern also works. # Verification and Attestation Attestation computes a Merkle root over confirmed files, signs it with the gateway's Ed25519 key, and anchors it on Solana as an SPL Memo. On-chain payload carries: `pid` (random public proof id), `sid` (deterministic snapshot id derived from `pid`, root, file count), `dg` (statement digest, SHA-256 of canonical envelope body), `root` (Merkle root hex without prefix), `n` (file count), `seq`, `t` (Unix timestamp), `k` (issuer key id). Monad anchor format is specified but the Monad bridge is not operational in production. When configured, the gateway also writes to a Solana Attestation Service account at a predictable address. **Trigger.** `POST /v1/storage/attest?receipt_id=X`. Signed, `ops: ["locker:attest"]`, envelope's `receipt_id` must equal query (else 401 `RECEIPT_ID_MISMATCH`). Only the receipt's payer may issue — the gateway spends server-held SOL on the anchor write. **Async default.** HTTP 202 with `{"status": "accepted", "receipt_id": "...", "message": "...", "verify_url": "/v1/storage/verify?receipt_id=..."}`. Poll `verify_url`. **Synchronous variant.** `?sync=true` polls server-side. If the ceiling (default 90 seconds) expires while work is in flight, the gateway returns 202 with `attestation_status`, `verify_url`, and — when available — the current `attestation` object (possibly with `attestation_is_prior: true`). **Lifecycle.** `pending` → `computed` → `pushing` → `complete`. A non-pending attestation with a truthy `merkle_root` is the completion signal. **Merkle spec `nukez-merkle-v1`** (canonical at `/docs/verify/merkle-v1.md`, machine-readable at `/specs/nukez-merkle-v1.json`): - Files sorted lexicographically by filename. - Leaf = `SHA256("{filename}:{size_bytes}:{content_hash_without_sha256_prefix}")` on UTF-8 bytes. - Parents = `SHA256((left_hex + right_hex).encode("utf-8"))` — concatenate lowercase hex, then hash. - Odd nodes at any level are duplicated. - All hashes lowercase hex; no separator between concatenated pair. Including `size_bytes` in the leaf lets a verifier detect equal-length content substitution and audit total bytes from leaf inputs alone. **`att_code`** is a 9-digit integer derived from `result_hash` (not `merkle_root`): strip `sha256:`, take first 12 hex chars, parse as integer, take modulo 1,000,000,000. Compact fingerprint, not a security primitive. `merkle_root` is the Ed25519-signed value written to the SPL Memo. `result_hash` is SHA-256 of the canonical JSON summary of the file manifest. **Public verification endpoints (no auth).** - `GET /v1/storage/verify?receipt_id=X` (also `POST`) — returns `receipt`, `attestation` (or null), `anchor`, `verification` (per-fact booleans: `commitment_computed`, `signature_valid`, `anchor_submitted`, `anchor_confirmed`, `anchor_finalized`, `payment_verified`, `bytes_checked`, `witness`), `verified` (true when a non-pending attestation exists), `locker_id`, `verify_url`, `attestation_hint`. Read-only. - `GET /v1/storage/merkle-proof?receipt_id=X&filename=Y` — returns `leaf_hash`, `leaf_index`, `merkle_root` (with `sha256:` prefix), `proof[]` (each `{hash, position}`), `tree_depth`, `file_count`, `file_entry`. Verify by walking the proof: for each step, if `position=="left"` then `sha256(sibling || current)`, else `sha256(current || sibling)`; final value (after stripping `sha256:`) must equal `merkle_root`. - `GET /v1/storage/verification-bundle?receipt_id=X` — self-contained: `payment_proof` (tx signature, paid amount, payer, payee, `explorer_url`), `content_proof` (`merkle_root`, `att_code`, `file_count`, `total_bytes`, per-file `download_url` at `/v1/r/{receipt_id}/f/{filename}`), `on_chain_anchor` (type, `slot`, `tx_signature`, explorer URLs), `merkle_algorithm` (pseudocode plus notes), `verify_yourself` (step-by-step). Portable — anyone with `receipt_id` can verify. - `GET /v1/storage/evidence-bundle?receipt_id=X` — portable evidence per `nukez-evidence-v2`: file list, commitment, signed envelope, provider-neutral `anchor`, separate verification facts, signing keys, verification steps. Historical Switchboard-era records export as `bundle_version 1`; `solana_sas_v1` and `solana_memo_v2` anchors must not be labeled Switchboard. Confirmed on-chain anchoring requires `verification.anchor_confirmed`. - `GET /v1/receipts/{receipt_id}/verify` — Ed25519 verification of `receipt_sig` over `receipt_hash` using `receipt_signer_pubkey`. - `GET /v1/receipts/{receipt_id}/verify-payment` — re-fetches tx from RPC and returns `binding_verified` plus per-check booleans (`tx_success`, `amount_match`, `payer_match`, `slot_match`, `treasury_received`), observed `on_chain` block, `receipt_claims`. - `GET /v1/keys` (also `/.well-known/nukez-signing-keys.json`) — gateway's statement-signing keys for out-of-band pinning. **Authenticated recompute.** `GET /v1/storage/recompute-verify?receipt_id=X` re-downloads every file, re-hashes, rebuilds the tree in memory, and compares to persisted attestation. **Requires a payer-signed envelope with `locker:read`.** Strictly read-only. Comparison is on `result_hash` values (prefixed `sha256:`), not raw Merkle roots. Response has `computed`, `stored`, `match`, `file_count`, `recompute_ms`, and optionally an `anchor` block. For content-addressed providers, the gateway trusts the manifest's `content_hash` and `provider_ref` rather than re-downloading. **Automatic re-attestation.** When enabled, successful `confirm_upload`, `confirm_upload_batch`, and `delete_file` operations enqueue a fresh attestation. Skipped when the manifest has not mutated since the last attestation. Fire-and-forget: never fails the triggering file operation. **Key pinning caveat.** Two distinct Ed25519 keys: the **receipt/manifest signer** (served as `receipt_signer_pubkey` on `GET /v1/receipts/{receipt_id}`) and the **short-URL token signer** (served on `GET /v1/short-url/verify-key`). They do not overlap. For trust-free verification, pin the receipt signer out of band. # Hosted Agent: NukezAgent NukezAgent is a hosted orchestration service. Describe a task in natural language; the service constructs unsigned envelopes and routes files. **Your private key never leaves your possession** — the service returns unsigned envelopes for you to sign locally, then replays your signatures to the gateway. Lockers are developer-owned: the gateway records your public key as owner. API base: `https://agent.nukez.xyz`; developer portal at `portal.nukez.xyz` is a separate web interface. Operational endpoints (base `/v1/`): - `POST /v1/delegate` — natural-language delegation; also handles signing completions. - `POST /v1/intent` — structured intents (`persist`, `recall`, `verify`). - `GET /v1/status` — session state, capacity, subscription (`subscription.active`, `subscription.expires_at`, `lockers[]` with `ordinal`, `file_count`, `used_bytes`, `capacity_bytes`, `is_full`). - `GET /v1/service/request`, `POST /v1/service/confirm` — subscription quote (HTTP 402) and activation. - `POST /v1/blob/init`, `POST /v1/blob/{upload_id}/part/{part_no}`, `POST /v1/blob/{upload_id}/finalize` — chunked upload returning a `data_token` for a subsequent `/v1/delegate` call, referenced in the task string as ``. - `GET /v1/download/{token}` — one-time redemption for recalled files. - `GET /v1/card`, `GET /.well-known/nukez-agent.json`, `GET /.well-known/agent.json` — discovery. - `GET /health` — no auth. **Two signing layers.** 1. **Request auth** — every operational call needs four headers: `X-Nukez-Identity` (base58 Ed25519 pubkey or `0x…` EVM address), `X-Nukez-Nonce` (32 random bytes hex, single-use per identity in the replay window), `X-Nukez-Timestamp` (Unix seconds, ±300 second window), and `X-Nukez-Signature` over the canonical 7-line message: ``` nukez-request:v1 method= path= identity= nonce= timestamp= body_sha256= ``` Lines joined with `\n`, no trailing newline. `body_sha256` for empty body is SHA-256 of empty bytes: `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`. Reused nonce returns 401 "Replay detected". Missing headers return 400 listing missing names. Bad signature returns 401. 2. **Gateway envelopes** — write/read operations return unsigned envelopes with `status: "signing_needed"`, `signing_request_id`, `envelopes[]` (each with `envelope`, `envelope_json`, `canonical_body`, `locker_id`), and `operation`. Sign each `envelope_json` verbatim (base58 for Ed25519, `0x`-hex for EVM) and POST `{"signing_request_id": "...", "signatures": ["...", ...]}` back to `/v1/delegate`. Envelope validity capped at 300 seconds (`exp − iat ≤ 300`). Successful persist returns `receipts[]` with `receipt_id`, `filename`, `content_hash`, `size_bytes`, `idempotent`, plus a `summary`. Small recall returns `payload` (base64); larger recall returns a `download_token`. Verify returns `verified`, `merkle_root`, `tx_signature`. A persist bundle is five envelopes: `[create_file, create_index, attest, confirm_data, confirm_index]`; recall requires one `locker:read` envelope; verify is unsigned. Resubmitting the same `signing_request_id` with the same signatures returns the cached result. **Multi-chain.** Ed25519 (base58) for Solana; secp256k1 EIP-191 personal-sign (`0x…` address, `0x`-prefixed 65-byte hex signature) for EVM. Gateway records the algorithm at receipt time and enforces consistency for subsequent envelope verifications. **Subscription pricing.** `GET /v1/service/request` (no auth) returns an x402 402 body with `x402Version: 2`, `price_usd`, `terms` (duration, bundled lockers, renewal type), `accepts[]` per supported asset/chain. Solana mainnet and Monad mainnet supported. Pay on-chain, then `POST /v1/service/confirm` with the tx signature in `X402-TX` (or `tx_signature` / `tx_sig` in body), plus `X-Nukez-Identity`, and optional `payment_asset`, `payment_chain`, `pay_req_id`. Successful activation returns `{"status": "active", "endpoint": "/v1/delegate", "service_expires_at": "...", "lockers_provisioned": N, "registration_token": "...", "portal_url": "..."}`. Renewal returns `status: "renewed"` and extends `service_expires_at` without re-provisioning. Do not hardcode amounts. **Portal.** Passkey (WebAuthn) sign-in via 4-step registration (`/v1/portal/auth/challenge` → `/v1/portal/auth/verify-keypair` → `/v1/portal/auth/passkey/register/options` → `/v1/portal/auth/passkey/register/complete`) and 2-step login (`/v1/portal/auth/passkey/login/options` → `/v1/portal/auth/passkey/login/complete`). Multiple keypairs (Ed25519 and EVM) can link to one portal identity via `/v1/portal/auth/link-pubkey`, `/link-pubkey/verify`, `/link-pubkey/complete`; `GET /v1/portal/auth/linked-keys` lists them. # Failure Modes (Expected) - **`tx_not_found` during confirm** — HTTP 402 with `error_code: TX_NOT_FOUND`, `details.verify.err == "tx_not_found"`. Retry with exponential backoff (2, 4, 8, 16, 32 seconds). Retry is safe because `receipt_id` is deterministic. Permanent 402 codes that must **not** be retried: `AMOUNT_MISMATCH`, `WRONG_RECIPIENT`, `QUOTE_EXPIRED`, `DUPLICATE_CONFIRM`, `INVALID_PAY_REQ`, `PAYMENT_VERIFICATION_FAILED`, `INSUFFICIENT_AMOUNT`, `INSUFFICIENT_PAYMENT`, `TX_FAILED`, `TX_REVERTED`, `PAYMENT_OUTSIDE_QUOTE_WINDOW`. - **Pay-leg mismatch** — HTTP 400 `" on not in quote"`. Send matching `pay_asset` / `pay_network`; default-leg fallback applies only when both are omitted. - **Payment option not in quote** — HTTP 422 `PAYMENT_OPTION_NOT_IN_QUOTE`. The echoed `pay_asset` / `pay_network` was not one of the legs offered in the quote's `accepts[]`. - **Payment outside quote window** — HTTP 402 `PAYMENT_OUTSIDE_QUOTE_WINDOW`. Tx not made for this quote (e.g., sent before quote existed). Request quote first, then pay, then confirm. - **Quote expiry** — HTTP 410 Gone with `error: "quote_expired"`, `expired_at`, `expired_ago_sec`. Request a new quote. - **Payment already allocated** — HTTP 409 `PAYMENT_ALREADY_ALLOCATED`. One tx buys one pay request; retrying the same pay request with the same tx is idempotent, but a second pay request needs its own tx. - **Confirm intent required** — 401 `CONFIRM_INTENT_REQUIRED` or `CONFIRM_INTENT_INVALID` when binding `operator_pubkey` without a valid `payer_signature`. The 401 detail carries `sign_this` — exact UTF-8 text to sign. - **Short URL expiry** — 403/404 from upload/download; 403 also on method mismatch. Re-mint via `GET /v1/lockers/{locker_id}/files/{filename}?ttl_min=30` or `POST /v1/lockers/{locker_id}/files/urls`. - **Envelope expiry** — 401 stale timestamp; rebuild with fresh `iat` / `exp` and re-sign. TTL > 300 seconds is rejected. - **Body-hash mismatch** — 401. Serialize canonically once; hash and send identical bytes. - **Nonce replay** — 409 `NONCE_REPLAY` when the cached-response window expired or original response was never cached. Use a fresh nonce. - **Content propagation (Arweave/Filecoin)** — download may return 404 `CONTENT_PROPAGATION_PENDING` with `retryable: true` and `suggested_delay: 15`; wait and retry. - **Idempotency conflict** — HTTP 409 on `/v1/storage/request` when same `Idempotency-Key` is reused with different body. - **Hash mismatch on confirm** — HTTP 409 `HASH_MISMATCH` when `expected_hash` at create differs from SHA-256 computed at confirm. - **Endpoint retired** — 410 `endpoint_retired` from `POST /v1/attest/simulate` (oracle simulation retired). Use `GET /v1/storage/evidence-bundle?receipt_id=...` or `GET /v1/storage/verify`. - **Asset missing from quote** — intended asset absent from `accepts[]` because its live price feed was unavailable or failed cross-check. Pay with another offered leg, or request a fresh quote. - **Transient 5xx** — retry with jitter; keep an `Idempotency-Key` on `/v1/storage/request`. On 5xx the nonce is released, so retry with the same envelope is safe. - **Rate limits** — HTTP 429 with `Retry-After`. Token-bucket keyed by client IP: 5 req/s sustained, burst 10. `/health` and `/metrics` are exempt. MCP-origin traffic is keyed per agent with its own independent bucket. **Uniform error envelope** on every failure: ```json { "error_code": "SNAKE_CASE", "message": "...", "details": { ... }, "request_id": "..." } ``` `request_id` also appears in `X-Request-ID` on every response (including successes). See: https://docs.nukez.xyz/docs/ERROR_RECOVERY.md