NavenDocs
NavenDocs
Introduction
IPFS StorageQuickstartAPI reference
Back to Naven Network
IPFS Storage

API reference

Public x402 image uploads, response fields, and runnable order recovery examples.

Base URL: https://api.naven.network/v1/ipfs.

MethodPathPurpose
GET/configAvailability, price, limits, and retention
POST/uploadMultipart file; 402 quote, then payment and image upload
GET/orders/{id}Query status using the original Idempotency-Key
POST/orders/{id}/reconcileReconcile a previously verified pending payment

For dependencies, wallet setup, and a complete upload script, start with the quickstart.

Authentication and idempotency

Every endpoint except /config requires Idempotency-Key, a randomly generated UUID. It identifies and authorizes one upload order. No account, project authentication, or Naven API key is required.

  • Generate the key once with crypto.randomUUID() or randomUUID from node:crypto.
  • Save it privately before sending the first upload request.
  • Reuse it with the same image bytes and media type when retrying. A different image requires a different key; changing the image under an existing key returns 409.
  • Save the order ID from the x402 challenge's resource.url query parameter orderId. The successful response also includes the ID as data.id.
  • Keep the key out of public URLs, source control, and logs. It grants access to the order.

The optional orderId query parameter on /upload must match the order resolved by the key. The payment payload must preserve the resource URL from the challenge, including its order ID.

Check availability

GET /config is a free request and needs no headers:

curl https://api.naven.network/v1/ipfs/config

The response uses the standard envelope { "code": 0, "message": "ok", "data": ... }. The data object includes:

FieldMeaning
availableWhether the upload service is ready to accept requests
upload.priceUsdDisplay price as a string; currently "0.005"
upload.maxFileBytesMaximum image size: 5000000
upload.maxPixelsMaximum width × height: 16000000
upload.mimeTypesimage/jpeg, image/png, and image/webp
upload.retentionDaysIncluded storage period: 365
delivery.includedImage delivery is included
delivery.renewalsAvailableWhether renewals are available; currently false

An unavailable service can still return HTTP 200 from /config; check data.available before uploading. The payable network, token, recipient, and exact atomic amount come from the upload's 402 challenge.

Upload an image

POST /upload accepts multipart/form-data with exactly one file field.

InputRequiredDescription
Idempotency-Key headerYesSaved UUID for this upload order
file form fieldYesStatic JPEG, PNG, or WebP image
PAYMENT-SIGNATURE headerFor paymentx402 v2 payment encoded by your client
orderId query parameterNoOrder ID, if supplied, must match the saved key

Images must be non-empty, at most 5,000,000 bytes, and at most 16 megapixels. Animated images are rejected. The entire multipart request must fit within 5,100,000 bytes. Let FormData or your HTTP library set Content-Type and its boundary.

Payment challenge

The first valid unpaid upload returns HTTP 402 with a base64-encoded PAYMENT-REQUIRED header and the x402 challenge in the JSON body. This is the expected payment step, not a completed upload. The challenge is an x402 object, not a Naven data envelope.

Challenge fieldMeaning
x402VersionProtocol version: 2
resource.urlCanonical upload URL with ?orderId=...; bind the payment to this exact URL
accepts[0].schemePayment scheme: exact
accepts[0].networkCAIP-2 network identifier; currently eip155:4663
accepts[0].assetUSDG token contract address on the requested network
accepts[0].amountExact amount as an atomic-unit string; currently "5000" (0.005 USDG)
accepts[0].payToRecipient of this upload payment
accepts[0].extraToken signing metadata used by the EVM client

Use the x402 SDK to sign the challenge and encode PAYMENT-SIGNATURE. Save the encoded payment before sending it, then repeat the multipart upload with the same key and image. A plain token transfer does not replace the signed x402 request.

Successful response

A completed upload returns HTTP 200. PAYMENT-RESPONSE contains the x402 settlement result after confirmed payment. The JSON body has this shape; angle-bracket values below stand for the real values returned for your image:

{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "f7bcd3e4-0d89-4f7f-9d65-c109a4882170",
    "status": "fulfilled",
    "amountAtomic": "5000",
    "retentionDays": 365,
    "transactionHash": "<payment-transaction-hash>",
    "quoteExpiresAt": "<ISO-8601-timestamp>",
    "expiresAt": "<ISO-8601-timestamp>",
    "paymentUrl": "https://api.naven.network/v1/ipfs/upload?orderId=f7bcd3e4-0d89-4f7f-9d65-c109a4882170",
    "cid": "<image-cid>",
    "ipfsUri": "ipfs://<image-cid>",
    "url": "https://assets.naven.network/ipfs/<image-cid>"
  }
}
Response fieldDescription
idUpload order ID; use it together with the original key for recovery
statusCurrent order state; see the table below
amountAtomicPayment amount in the token's smallest units, returned as a string
retentionDaysIncluded storage duration
transactionHashConfirmed payment transaction hash, or null before confirmation
quoteExpiresAtExpiry of the unpaid quote; this is not the storage expiry
expiresAtEnd of the included storage period, or null before upload completion
paymentUrlCanonical payment resource URL for this order
cid, ipfsUri, urlPublic image identifiers and URL, returned only when status is fulfilled; otherwise null

Always use the returned url for sharing or embedding instead of constructing a URL from an assumed host. Pending orders do not publish partial storage URLs.

Query an order

GET /orders/{id} takes the original Idempotency-Key header and returns the same order fields inside data. It does not create a payment or require a wallet signature. A missing order or a key that does not authorize that order returns 404.

StatusMeaning and next action
awaiting_paymentPayment has not been confirmed. Continue the saved upload with its original quote and any saved authorization.
payment_pendingA verified payment is awaiting confirmation. Query or reconcile it; do not sign another payment.
paidPayment is confirmed but storage is incomplete. Repeat the upload with the original file and key; no new signature is needed.
fulfilledUpload is complete. Use the returned image URL, CID, and expiry date.
expiredThe stored image's retention has ended. Public image fields are no longer returned.

Unpaid quotes last one hour. Check quoteExpiresAt as well as status: an unpaid order can still report awaiting_payment after its quote has expired, and attempting to pay it returns 410. Paid orders remain redeemable; their storage year begins when the upload succeeds.

Recover a payment

POST /orders/{id}/reconcile accepts JSON and the original key:

{ "transactionHash": "0x<64-hex-character-payment-transaction-hash>" }

Use the transaction hash of the payment associated with this order. Reconciliation only confirms a previously verified pending payment; it does not accept an unrelated transfer, charge your wallet, or upload the image. One payment authorization or transaction cannot fund two orders.

Save the following as recover.ts beside the quickstart script. It reads the saved key and order ID, checks the order, and optionally submits a transaction hash for reconciliation. It uses Bun's built-in APIs and needs no wallet private key or additional imports beyond the one shown:

import { readFile } from "node:fs/promises";

const api = "https://api.naven.network/v1/ipfs";

async function readData(response: Response) {
  const body = await response.json();
  if (!response.ok || body.code !== 0) {
    throw new Error(`HTTP ${response.status}: ${body.message ?? "Request failed"}`);
  }
  return body.data;
}

async function main() {
  const [statePath, transactionHash] = process.argv.slice(2);
  if (!statePath) {
    throw new Error("Usage: bun run recover.ts ./image.png.naven-upload.json [transactionHash]");
  }
  const { key, orderId } = JSON.parse(await readFile(statePath, "utf8"));
  if (!key || !orderId) throw new Error("The saved upload has no order ID yet");
  if (transactionHash && !/^0x[0-9a-f]{64}$/i.test(transactionHash)) {
    throw new Error("Provide a 0x-prefixed, 64-hex-character transaction hash");
  }

  const orderUrl = `${api}/orders/${encodeURIComponent(orderId)}`;
  let order = await readData(await fetch(orderUrl, {
    headers: { "Idempotency-Key": key },
  }));

  if (transactionHash && order.status === "payment_pending") {
    order = await readData(await fetch(`${orderUrl}/reconcile`, {
      method: "POST",
      headers: { "Idempotency-Key": key, "Content-Type": "application/json" },
      body: JSON.stringify({ transactionHash }),
    }));
  }
  console.log(JSON.stringify(order, null, 2));
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : "Recovery failed");
  process.exitCode = 1;
});

Check the order without submitting a transaction:

bun run recover.ts ./image.png.naven-upload.json

If it is payment_pending and you have the payment's transaction hash, pass the actual hash in place of 0xYOUR_PAYMENT_TRANSACTION_HASH:

bun run recover.ts ./image.png.naven-upload.json 0xYOUR_PAYMENT_TRANSACTION_HASH

If the payment cannot yet be confirmed, reconciliation returns 409 and the order remains pending. Query again or retry reconciliation later without sending a new payment. If you do not have a transaction hash, keep the saved state and check the order again; a request timeout alone does not mean payment failed.

Once the order becomes paid, finish storing the image with the original command:

bun run upload.ts ./image.png

Errors and retries

Errors normally use { "code": <number>, "message": "...", "data": null }. The initial 402 challenge is the exception described above. Always check the HTTP status before reading success fields.

HTTP statusMeaningNext action
400Invalid key, body, payment proof, or file fieldCorrect the request; preserve an existing order's key and file
402Payment required or verification failedHandle a valid initial challenge; for a failed saved payment, inspect the order before signing again
404Order not found for this keyCheck that both the ID and key come from the same saved upload
409Changed file, upload in progress, or uncertain paymentRestore the original image, wait for the active upload, or inspect/reconcile the order as indicated by the message
410Unpaid quote expiredCreate a new order only after confirming the old one was unpaid with no uncertain settlement
413File or multipart request exceeds its limitUse a smaller image for a new upload
415Unsupported/invalid image, animation, or more than 16 MPUse a supported static image within the limits
502Upload request or storage failedInspect the saved order and retry with the same key and image; a confirmed paid order is not charged again
503Upload service temporarily unavailableCheck /config and retry later with the saved state

Do not generate a new key or sign another payment to bypass a pending order. There are no bandwidth purchase, project management, per-image pause, or renewal endpoints.

Quickstart

Run a complete TypeScript example to pay for an image upload and recover interrupted requests.

Marketplace

Request crypto market and token-risk data with a wallet payment for each request.

On this page

Authentication and idempotencyCheck availabilityUpload an imagePayment challengeSuccessful responseQuery an orderRecover a paymentErrors and retries