{"openapi":"3.1.0","info":{"title":"Store | nosub.club Mainnet API","version":"1.0.0","x-guidance":"This API stores public files on Walrus after payment. Choose an upload method based on file size:\n\nMETHOD 1 — Inline upload (files under ~1 MB raw): POST /v1/files/public with Content-Type: application/octet-stream and raw bytes, or with a JSON body containing contentBase64 or contentText, plus a duration query parameter. On success returns HTTP 201 with blobId, storageObjectId, aggregatorUrl, and expiresAt. Note: base64 encoding adds ~33% overhead, so a 1 MB file becomes ~1.37 MB in the request body; prefer the upload session method for anything larger.\n\nMETHOD 2 — Upload session (files up to 10 MB): POST /v1/files/public with a JSON body containing maxSizeBytes, contentType, and duration. On success returns HTTP 200 with an uploadUrl, method (PUT), expiresAt, maxSizeBytes, and contentType. Then PUT the raw file bytes directly to that uploadUrl before expiresAt, using the same contentType you declared (not application/octet-stream) and a body no larger than maxSizeBytes. The session is single-use and needs no payment or auth header on the PUT.\n\nFor both methods, a 402 response is expected before payment and contains the authoritative payment challenge; after paying, retry the same POST request with the payment proof. To renew storage for a blob created by this service, call POST /v1/files/public/{blobId}/extend with a JSON body containing duration and, when available, storageObjectId. Pricing is advertised as dynamic USD; runtime 402 challenges remain authoritative for exact settlement parameters."},"servers":[{"url":"https://store.nosub.club"}],"security":[],"x-service-info":{"categories":["storage","developer-tools"],"networkProfile":"mainnet","walrus":{"network":"mainnet","epochDurationDays":14,"durationToEpochs":"ceil(durationDays / 14)"},"docs":{"homepage":"https://store.nosub.club","apiReference":"https://store.nosub.club/openapi.json"}},"paths":{"/health":{"get":{"summary":"Service health","description":"Free health check for service readiness.","security":[],"responses":{"200":{"description":"Service is healthy.","content":{"application/json":{"schema":{"type":"object","required":["status","checks"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"checks":{"type":"object","additionalProperties":true},"payments":{"type":"object","additionalProperties":true},"publisher":{"type":"object","additionalProperties":true}}}}}},"503":{"description":"Service is degraded.","content":{"application/json":{"schema":{"type":"object","required":["status","checks"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"checks":{"type":"object","additionalProperties":true},"payments":{"type":"object","additionalProperties":true},"publisher":{"type":"object","additionalProperties":true}}}}}}}}},"/v1/files/public":{"post":{"summary":"Upload a public file to Walrus storage","description":"Upload a public file to Walrus storage. Supports direct upload for small files and paid upload sessions for agent clients or large files. Runtime 402 challenges are authoritative for exact payment parameters.","security":[{"x402":[]}],"x-walrus-storage":{"network":"mainnet","epochDurationDays":14,"durationToEpochs":"ceil(durationDays / 14)"},"parameters":[{"name":"duration","in":"query","required":true,"schema":{"type":"integer","minimum":1,"maximum":365,"default":30},"example":30,"description":"Requested storage duration in days. Provide it as a query parameter. JSON uploads may also include duration in the body, but it must match this query value. The service converts this to Walrus epochs by rounding up with 14-day epochs on mainnet."},{"name":"contentType","in":"query","required":false,"schema":{"type":"string","example":"text/plain"},"description":"MIME type of the uploaded content (e.g. text/plain, application/json, application/msword). When provided, the value is stored as metadata on the Walrus blob object and the returned aggregatorUrl will serve the file with the correct Content-Type header."}],"x-payment-info":{"price":{"mode":"dynamic","currency":"USD","min":"0.01","max":"100.00"},"protocols":[{"x402":{}},{"mpp":{"method":"tempo","intent":"charge","currency":"0x20c000000000000000000000b9537d11c60e8b50"}}],"offers":[{"intent":"charge","method":"x402","amount":null,"currency":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","description":"USDC on base. Exact amount is returned by the runtime 402 challenge."},{"intent":"charge","method":"tempo","amount":null,"currency":"0x20c000000000000000000000b9537d11c60e8b50","description":"USDC.e on Tempo. Exact amount and payment parameters are returned by the runtime 402 challenge."}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"anyOf":[{"required":["contentBase64"]},{"required":["contentText"]},{"required":["maxSizeBytes","contentType"]}],"properties":{"duration":{"type":"integer","minimum":1,"maximum":365,"description":"Requested storage duration in days. Provide it as a query parameter. JSON uploads may also include duration in the body, but it must match this query value. The service converts this to Walrus epochs by rounding up with 14-day epochs on mainnet."},"contentBase64":{"type":"string","description":"Base64-encoded file bytes to store. Suitable for files under ~1 MB raw; base64 encoding adds ~33% overhead. Use the upload session method (maxSizeBytes) for larger files."},"contentText":{"type":"string","description":"UTF-8 text content to store. Suitable for small text content. Use the upload session method (maxSizeBytes) for large text files."},"contentType":{"type":"string","example":"text/plain","description":"MIME type of the uploaded content. Stored as metadata and used by the returned aggregatorUrl."},"maxSizeBytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"Declared maximum byte size for a paid upload session (METHOD 2). Triggers the two-step flow: this POST returns HTTP 200 with an uploadUrl; then PUT the raw file bytes to that URL. Use this for files larger than ~1 MB. The actual PUT payload may be smaller but must not exceed this value."}}},"examples":{"text":{"summary":"Small text file","value":{"contentText":"hello","contentType":"text/plain"}},"base64":{"summary":"Base64 file bytes","value":{"contentBase64":"aGVsbG8=","contentType":"text/plain"}},"session":{"summary":"Upload session","value":{"maxSizeBytes":887296,"contentType":"audio/wav","duration":30}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary","maxLength":10485760},"example":"hello"}}},"responses":{"200":{"description":"Upload session created (two-step upload). Returned when the request body contains maxSizeBytes. PUT the raw file bytes to the returned uploadUrl before expiresAt using the declared contentType.","content":{"application/json":{"schema":{"type":"object","required":["uploadUrl","method","expiresAt","maxSizeBytes","contentType"],"additionalProperties":false,"properties":{"uploadUrl":{"type":"string","format":"uri"},"method":{"type":"string","const":"PUT"},"expiresAt":{"type":"string","format":"date-time"},"maxSizeBytes":{"type":"integer","minimum":1},"contentType":{"type":"string"}}}}}},"201":{"description":"File stored successfully (inline upload). Returned when the request body contains raw bytes, contentBase64, or contentText. Contains blobId, storageObjectId, aggregatorUrl, and expiresAt.","content":{"application/json":{"schema":{"type":"object","required":["blobId","storageObjectId","expiresAt"],"properties":{"blobId":{"type":"string"},"storageObjectId":{"type":"string"},"aggregatorUrl":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"invalid_request"},"message":{"type":"string"}}}}}},"402":{"description":"Payment Required. Pricing is derived from actual payload bytes for direct uploads, or declared maxSizeBytes for upload sessions.","headers":{"PAYMENT-REQUIRED":{"required":false,"schema":{"type":"string"},"description":"Base64-encoded x402 v2 PaymentRequired payload. Agents sign one accepted requirement and retry with X-Payment."},"WWW-Authenticate":{"required":false,"schema":{"type":"string"},"description":"MPP Payment challenge. The realm is the public API hostname and agents retry with an Authorization Payment credential."}},"content":{"application/json":{"schema":{"type":"object","required":["error","message","intentId","expiresAt","offers"],"properties":{"error":{"type":"string","const":"payment_required"},"message":{"type":"string"},"intentId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"offers":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["protocol","network","asset","amount","payTo"],"properties":{"protocol":{"type":"string"},"network":{"type":"string"},"asset":{"type":"string"},"amount":{"type":"string"},"payTo":{"type":"string"},"intentId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}},"x402":{"type":"object","additionalProperties":true}}}}}},"413":{"description":"File too large.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"payload_too_large"},"message":{"type":"string"}}}}}},"503":{"description":"Pricing or storage dependency unavailable.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"pricing_unavailable"},"message":{"type":"string"}}}}}}}}},"/v1/files/public/uploads/{token}":{"put":{"summary":"Upload bytes for a paid upload session","description":"Stores bytes for a short-lived, single-use upload session URL issued by POST /v1/files/public after payment. No payment header is required; settlement happened on the POST. The request Content-Type MUST equal the contentType declared when the session was created (e.g. audio/wav), not application/octet-stream; a mismatch is rejected with 400. The body must not exceed the session's maxSizeBytes.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"description":"Raw file bytes sent with the Content-Type declared at session creation.","content":{"*/*":{"schema":{"type":"string","format":"binary","maxLength":10485760}}}},"responses":{"201":{"description":"File stored successfully.","content":{"application/json":{"schema":{"type":"object","required":["blobId","storageObjectId","expiresAt"],"properties":{"blobId":{"type":"string"},"storageObjectId":{"type":"string"},"aggregatorUrl":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid, oversized, mismatched, or already-used upload session.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"invalid_request"},"message":{"type":"string"}}}}}},"410":{"description":"Upload session expired.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"gone"},"message":{"type":"string"}}}}}},"503":{"description":"Storage dependency unavailable.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"publisher_unavailable"},"message":{"type":"string"}}}}}}}}},"/v1/files/public/{blobId}/extend":{"post":{"summary":"Extend public file storage","description":"Extends a file previously stored by this service after payment. Runtime 402 challenges are authoritative for exact payment parameters.","security":[{"x402":[]}],"x-walrus-storage":{"network":"mainnet","epochDurationDays":14,"durationToEpochs":"ceil(durationDays / 14)"},"parameters":[{"name":"blobId","in":"path","required":true,"schema":{"type":"string","minLength":1},"example":"0xabc123"}],"x-payment-info":{"price":{"mode":"dynamic","currency":"USD","min":"0.01","max":"100.00"},"protocols":[{"x402":{}},{"mpp":{"method":"tempo","intent":"charge","currency":"0x20c000000000000000000000b9537d11c60e8b50"}}],"offers":[{"intent":"charge","method":"x402","amount":null,"currency":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","description":"USDC on base. Exact amount is returned by the runtime 402 challenge."},{"intent":"charge","method":"tempo","amount":null,"currency":"0x20c000000000000000000000b9537d11c60e8b50","description":"USDC.e on Tempo. Exact amount and payment parameters are returned by the runtime 402 challenge."}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["duration"],"properties":{"duration":{"type":"integer","minimum":1,"maximum":365,"description":"Requested storage duration in days. Provide it as a query parameter. JSON uploads may also include duration in the body, but it must match this query value. The service converts this to Walrus epochs by rounding up with 14-day epochs on mainnet."},"storageObjectId":{"type":"string","minLength":1}}},"examples":{"extend":{"summary":"Extend for 90 days","value":{"duration":90}},"extendWithStorageObject":{"summary":"Extend with recorded storage object","value":{"duration":90,"storageObjectId":"0xstorage-object"}}}}}},"responses":{"200":{"description":"File storage extended successfully.","content":{"application/json":{"schema":{"type":"object","required":["blobId","storageObjectId","expiresAt"],"properties":{"blobId":{"type":"string"},"storageObjectId":{"type":"string"},"aggregatorUrl":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"invalid_request"},"message":{"type":"string"}}}}}},"402":{"description":"Payment Required","headers":{"PAYMENT-REQUIRED":{"required":false,"schema":{"type":"string"},"description":"Base64-encoded x402 v2 PaymentRequired payload. Agents sign one accepted requirement and retry with X-Payment."},"WWW-Authenticate":{"required":false,"schema":{"type":"string"},"description":"MPP Payment challenge. The realm is the public API hostname and agents retry with an Authorization Payment credential."}},"content":{"application/json":{"schema":{"type":"object","required":["error","message","intentId","expiresAt","offers"],"properties":{"error":{"type":"string","const":"payment_required"},"message":{"type":"string"},"intentId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"offers":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["protocol","network","asset","amount","payTo"],"properties":{"protocol":{"type":"string"},"network":{"type":"string"},"asset":{"type":"string"},"amount":{"type":"string"},"payTo":{"type":"string"},"intentId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}},"x402":{"type":"object","additionalProperties":true}}}}}},"404":{"description":"Blob not found.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"blob_not_found"},"message":{"type":"string"}}}}}},"410":{"description":"File has expired or receipt resume window elapsed.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"gone"},"message":{"type":"string"}}}}}},"503":{"description":"Pricing or storage dependency unavailable.","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","default":"pricing_unavailable"},"message":{"type":"string"}}}}}}}}}},"components":{"securitySchemes":{"x402":{"type":"http","scheme":"x402","description":"x402 v2 payment proof. Call the endpoint without this header first; the 402 response returns the PAYMENT-REQUIRED challenge to sign."}}}}