ar.io Logoar.io Documentation
TurboUpload Service

Upload

Data item upload endpoints (single and multi-part)

Posts a signed ANS-104 data item OR raw data to Arweave

Two Upload Modes:

1. Traditional ANS-104 Upload (Client-Signed)

Client creates and signs ANS-104 data item before uploading. The service detects ANS-104 format by checking the first 2 bytes (signature type 1-8).

Payment options:

  • Traditional balance (user has pre-loaded credits)
  • x402 payment (include X-PAYMENT header with EIP-3009 authorization)

2. Raw Data Upload (Server-Signed, x402 Only)

Simplified flow for AI agents and applications without ANS-104 signing capabilities. Requires RAW_DATA_UPLOADS_ENABLED=true on server.

The service automatically detects raw data (non-ANS-104) and:

  • Returns 402 Payment Required if no X-PAYMENT header provided
  • Creates and signs ANS-104 data item server-side using RAW_DATA_ITEM_JWK wallet
  • Adds attribution tags: Bundler, Upload-Type, Payer-Address, Upload-Timestamp, Content-Type
  • Validates x402 payment against payer's Ethereum address (not the signer)
  • Returns response with both owner (server wallet) and payer (Ethereum address) fields

Request formats for raw data:

  • Binary upload with X-Tag-* headers for custom tags
  • JSON envelope: {"data": "<base64>", "contentType": "...", "tags": [...]}

When using X-PAYMENT header, Content-Length must be present to enable fraud detection.

POST
/tx

Header Parameters

content-length?integer
Formatint64
Rangevalue <= 4294967296
content-type?string
Value in"application/octet-stream"
X-PAYMENT?string

Base64-encoded x402 payment authorization for pay-as-you-go USDC payments.

x402 Payment Flow:

  1. Upload without X-PAYMENT → Server returns 402 with payment requirements
  2. Client creates EIP-3009 authorization and signs with EIP-712
  3. Client retries upload with X-PAYMENT header containing base64(JSON)
  4. Server verifies signature, settles USDC transfer, processes upload
  5. Server returns receipt with x402Payment object and X-Payment-Response header

Decoded Structure (see X402PaymentHeader schema):

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x...(EIP-712 signature)...",
    "authorization": {
      "from": "0xPayerAddress",
      "to": "0xBundlerPaymentAddress",
      "value": "30490",
      "validAfter": 0,
      "validBefore": 1735689600,
      "nonce": "0x...(32 bytes hex)..."
    }
  }
}

Creating the Header:

const payload = { x402Version: 1, scheme: "exact", network: "base", payload: {...} };
const xPayment = Buffer.from(JSON.stringify(payload)).toString("base64");
headers["X-PAYMENT"] = xPayment;

Requirements:

  • Content-Length header MUST be present when using X-PAYMENT
  • Authorization value must match or exceed the 402 response's maxAmountRequired
  • Nonce must be unique (32-byte random hex string)

Supported Networks: base, base-sepolia, ethereum-mainnet, polygon-mainnet

Standards:

Formatbyte
X-Tag-*?string

Custom tags for raw data uploads (server-signed mode only). Use pattern X-Tag-Name where Name becomes the tag name. Example: X-Tag-App-Name: MyApp creates tag {"name": "App-Name", "value": "MyApp"}

Auto-added tags for raw uploads:

  • Bundler: Service name
  • Upload-Type: "raw-data-x402"
  • Payer-Address: Ethereum address from X-PAYMENT
  • Upload-Timestamp: Unix timestamp
  • Content-Type: From Content-Type header
  • X402-Tx-Hash: Blockchain transaction hash (if x402 payment)
  • X402-Payment-ID: Unique payment identifier UUID (if x402 payment)
  • X402-Network: Payment network e.g. "base" (if x402 payment)

A signed ANS-104 data item (binary) OR raw data (binary/JSON envelope for server-signing)

bodyfile

Traditional ANS-104 data item (client-signed)

Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/tx" \  -H "content-length: 4294967296" \  -H "content-type: application/octet-stream" \  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHgxMjM0NTY3ODkwYWJjZGVmLi4uIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjBiRWIwIiwidG8iOiIweENGZDNmOTk2NDQ3YTU0MUNiZmJhNTQyMjMxMEVEYjQxN2Q5ZjJjRTYiLCJ2YWx1ZSI6IjMwNDkwIiwidmFsaWRBZnRlciI6MCwidmFsaWRCZWZvcmUiOjE3MzU2ODk2MDAsIm5vbmNlIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVmIn19fQ==" \  -H "X-Tag-*: MyApp" \  -H "Content-Type: application/json" \  -d '{}'

Traditional signed ANS-104 data item upload paid via x402 USDC payment. Client signs data item with their wallet, includes X-PAYMENT header with USDC authorization.

{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "timestamp": 1700590909589,
  "version": "0.2.0",
  "owner": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "deadlineHeight": 1310000,
  "dataCaches": [
    "arweave.net"
  ],
  "fastFinalityIndexes": [
    "arweave.net"
  ],
  "winc": "1000000",
  "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQj...",
  "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqpts...",
  "x402Payment": {
    "paymentId": "550e8400-e29b-41d4-a716-446655440000",
    "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
    "network": "base-mainnet",
    "mode": "payg"
  }
}

"Data Item Exists"

"Invalid Content Type"

{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base-mainnet",
      "maxAmountRequired": "1000000",
      "resource": "/v1/tx",
      "description": "Upload 1024 bytes to Arweave via AR.IO Bundler",
      "mimeType": "application/json",
      "payTo": "0x6A0A10FFD285c971B841bee8892878c0d583Bf67",
      "maxTimeoutSeconds": 300,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ],
  "error": "Insufficient balance"
}

Posts a signed data item to arweave for a specific token

Uploads a signed ANS-104 data item to the bundler with token-specific handling. Payment can be handled via:

  • Traditional balance (user has pre-loaded credits)
  • x402 payment (include X-PAYMENT header with EIP-3009 authorization)

When using X-PAYMENT header, Content-Length must be present to enable fraud detection.

POST
/tx/:token

Path Parameters

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"

Header Parameters

content-length?integer
Formatint64
Rangevalue <= 4294967296
content-type?string
Value in"application/octet-stream"
X-PAYMENT?string

Base64-encoded x402 payment authorization for pay-as-you-go USDC payments.

x402 Payment Flow:

  1. Upload without X-PAYMENT → Server returns 402 with payment requirements
  2. Client creates EIP-3009 authorization and signs with EIP-712
  3. Client retries upload with X-PAYMENT header containing base64(JSON)
  4. Server verifies signature, settles USDC transfer, processes upload
  5. Server returns receipt with x402Payment object and X-Payment-Response header

Decoded Structure (see X402PaymentHeader schema):

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x...(EIP-712 signature)...",
    "authorization": {
      "from": "0xPayerAddress",
      "to": "0xBundlerPaymentAddress",
      "value": "30490",
      "validAfter": 0,
      "validBefore": 1735689600,
      "nonce": "0x...(32 bytes hex)..."
    }
  }
}

Creating the Header:

const payload = { x402Version: 1, scheme: "exact", network: "base", payload: {...} };
const xPayment = Buffer.from(JSON.stringify(payload)).toString("base64");
headers["X-PAYMENT"] = xPayment;

Requirements:

  • Content-Length header MUST be present when using X-PAYMENT
  • Authorization value must match or exceed the 402 response's maxAmountRequired
  • Nonce must be unique (32-byte random hex string)

Supported Networks: base, base-sepolia, ethereum-mainnet, polygon-mainnet

Standards:

Formatbyte

A signed data item

bodyfile
Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/tx/:token" \  -H "content-length: 4294967296" \  -H "content-type: application/octet-stream" \  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHgxMjM0NTY3ODkwYWJjZGVmLi4uIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjBiRWIwIiwidG8iOiIweENGZDNmOTk2NDQ3YTU0MUNiZmJhNTQyMjMxMEVEYjQxN2Q5ZjJjRTYiLCJ2YWx1ZSI6IjMwNDkwIiwidmFsaWRBZnRlciI6MCwidmFsaWRCZWZvcmUiOjE3MzU2ODk2MDAsIm5vbmNlIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVmIn19fQ==" \  -H "Content-Type: application/octet-stream" \  -d 'string'
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "owner": "8wgRDgvYOrtSaWEIV21g0lTuWDUnTu4_iYj4hmA7PI0",
  "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "dataCaches": [
    "arweave.net"
  ],
  "fastFinalityIndexes": [
    "arweave.net"
  ],
  "deadlineHeight": 1310000,
  "timestamp": 1700590909589,
  "version": "0.1.0",
  "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQjKXuT4lTngRFBFKO5NQT1iIfqSDKcbTRL6gJowM_L7bBQZRGkojzXD0PNNU2F0bNNJ80VtUktHifGTXbCgz5kiFciL19n0P3nX6ZfXnOn-H8ALZzRJV69apdvwqitpNKxLMPyc-QA0QBxmC3CKPz_7fy2Qg0QHr5g_ZT2Of-YJ_RsZTEoc3g1fgzsEmMBPOsx4XtPrhV6llnA3pncngzHbPdFvypdWiO8Bvr0EWmazNsoanuwK5uKJ_ROIGXW_dBBGN8Vrfv5U6dJnhJVn5IE7JFlixpFTluF_ICRzbUq2pk_re6jEtW1H3ItH2iN0UeFUw1uDbq3HJW6lDc8aOwDwDspJI11KEI6uCz5QmQy2V8DvRknoqcxmuihF6XmmJIZgTVeo6LNufEis9kFxqtc3Dh_gn8z0cDXKEKFycudckmcHP7vkWD68uSssMMJIdVgwvPZss06svfRnI-E33j3MrQI9FzMIv-7Df8iYATyeyldM1v3gexG0kQm0AMG1_8_SLqwu2QlqzM41mrK5vNmQxOVdIQSOPWPvzbF-YGRwpCjlveRBuARGC9JNC4UipvDYri2gRWuBx2uDL7dmVFv1gRll3dYNMYaMULHYngtrrCynB3Cyfhh7cyPlwuNlk0",
  "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqptsCiqNOtB3RKUToSX5hkPD45fJDY4057XkkcsQRuGsU8y9rgm37i-Kiyd5Z_iy6pJXrwi9XnAgGL118lIV790GZ7xe5o3DvPV3Px74C0ABsfL9lW86D4t_qClJ6wSQksKNd7rnUImIvHW0vxLswST7dfUngevzKt4kv48VTub4951XdUHjb45Uurf7xFYSCizAGtGqr5GYDFrVk-mNrzFH5bXt06PJfxe9E5ujIE5Uq1Az6vqEOO0E1mWmXqdTPluAxcjmgktkoNLHZnnU-BsYuFaTWW5NU3aS-RgJKXYs9O6Dc1-2SITl-H_wtdGNSj31fj72UkaAbkpA1mionK-8bOIkSpYgKCyTC42oHh1Fw4SLXPyLxBj1w6F32LSLjpse5dmKymj4fJPezCMdi709uIiVT7XOm4LZBCzFOxS6-UNxgE57dBFpcWkcqNO3p00biYqH5d5bzvK3bwd-4j0KyKlqPYTProSnd3P6ROZuuJaLoLD_Or5-L_dUjawHz-DFlzmckYaf8l3XdzPM4JSsE8CEDZl0NCN0AidXt_wjbr6k9JsO7cnLB226AjxhcyuxZOlhgkIn7EbxpVXx-O2mXkcXF8PixCP0k5brtriLeF1MAdspgd_S-LKQeGVr7-mk",
  "x402Payment": {
    "paymentId": "550e8400-e29b-41d4-a716-446655440000",
    "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
    "network": "base-mainnet",
    "mode": "payg"
  }
}

"Data Item Exists"
"Insufficient balance"

Posts a signed ANS-104 data item with x402 payment

x402-Specific Upload Endpoint for Signed Data Items

This endpoint is identical to /tx but explicitly signals x402 payment intent. It accepts signed ANS-104 data items and processes x402 USDC payments.

Use Case: Clients that want to use x402 payments for signed data item uploads.

Payment Flow:

  1. Client uploads without X-PAYMENT header → Server returns 402 Payment Required
  2. Server response includes x402 payment requirements (USDC amount, recipient, network)
  3. Client creates EIP-3009 authorization and signs with EIP-712
  4. Client retries upload with X-PAYMENT header (base64-encoded JSON)
  5. Server verifies payment signature, settles USDC transfer on-chain
  6. Server accepts upload and returns receipt with x402Payment object

Requirements:

  • Content-Type: application/octet-stream
  • Content-Length: Required (for fraud detection with x402 payments)
  • X-PAYMENT: Required for x402 payment authorization (after 402 response)

Supported Networks:

  • base (Base mainnet) - Default, enabled
  • base-sepolia (Base Sepolia testnet) - Requires X402_BASE_TESTNET_ENABLED=true
  • ethereum-mainnet - Requires X402_ETH_ENABLED=true
  • polygon-mainnet - Requires X402_POLYGON_ENABLED=true

Note: This endpoint has the same behavior as /tx - the separate path is for clarity and allows clients to explicitly signal x402 payment intent.

POST
/x402/upload/signed

Header Parameters

content-length?integer
Formatint64
Rangevalue <= 4294967296
content-type?string
Value in"application/octet-stream"
X-PAYMENT?string

Base64-encoded x402 payment authorization for pay-as-you-go USDC payments.

x402 Payment Flow:

  1. Upload without X-PAYMENT → Server returns 402 with payment requirements
  2. Client creates EIP-3009 authorization and signs with EIP-712
  3. Client retries upload with X-PAYMENT header containing base64(JSON)
  4. Server verifies signature, settles USDC transfer, processes upload
  5. Server returns receipt with x402Payment object and X-Payment-Response header

Decoded Structure (see X402PaymentHeader schema):

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x...(EIP-712 signature)...",
    "authorization": {
      "from": "0xPayerAddress",
      "to": "0xBundlerPaymentAddress",
      "value": "30490",
      "validAfter": 0,
      "validBefore": 1735689600,
      "nonce": "0x...(32 bytes hex)..."
    }
  }
}

Creating the Header:

const payload = { x402Version: 1, scheme: "exact", network: "base", payload: {...} };
const xPayment = Buffer.from(JSON.stringify(payload)).toString("base64");
headers["X-PAYMENT"] = xPayment;

Requirements:

  • Content-Length header MUST be present when using X-PAYMENT
  • Authorization value must match or exceed the 402 response's maxAmountRequired
  • Nonce must be unique (32-byte random hex string)

Supported Networks: base, base-sepolia, ethereum-mainnet, polygon-mainnet

Standards:

Formatbyte

A signed ANS-104 data item (binary)

bodyfile

ANS-104 data item signed with Arweave, Ethereum, or Solana key

Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/x402/upload/signed" \  -H "content-length: 4294967296" \  -H "content-type: application/octet-stream" \  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHgxMjM0NTY3ODkwYWJjZGVmLi4uIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjBiRWIwIiwidG8iOiIweENGZDNmOTk2NDQ3YTU0MUNiZmJhNTQyMjMxMEVEYjQxN2Q5ZjJjRTYiLCJ2YWx1ZSI6IjMwNDkwIiwidmFsaWRBZnRlciI6MCwidmFsaWRCZWZvcmUiOjE3MzU2ODk2MDAsIm5vbmNlIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVmIn19fQ==" \  -H "Content-Type: application/octet-stream" \  -d 'string'

{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "timestamp": 1700590909589,
  "version": "0.2.0",
  "owner": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "deadlineHeight": 1310000,
  "dataCaches": [
    "arweave.net"
  ],
  "fastFinalityIndexes": [
    "arweave.net"
  ],
  "winc": "1000000",
  "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQj...",
  "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqpts...",
  "x402Payment": {
    "paymentId": "550e8400-e29b-41d4-a716-446655440000",
    "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
    "network": "base",
    "mode": "payg"
  }
}

"Data Item Exists"

"Invalid Content Type"

{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "1000000",
      "resource": "/v1/x402/upload/signed",
      "description": "Upload 1024 bytes to Arweave via AR.IO Bundler",
      "mimeType": "application/json",
      "payTo": "0x6A0A10FFD285c971B841bee8892878c0d583Bf67",
      "maxTimeoutSeconds": 300,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ],
  "error": "Payment required"
}
"Internal server error"

Posts a signed ANS-104 data item with x402 payment (alias of /x402/upload/signed)

Alias of /x402/upload/signed. Identical behavior and handler (dataItemRoute) — accepts a signed ANS-104 data item and processes an x402 USDC payment. Provided as an alternate path for clients that prefer the /x402/data-item/* naming. See /x402/upload/signed for the full payment flow, requirements and supported networks.

POST
/x402/data-item/signed

Header Parameters

content-length?integer
Formatint64
Rangevalue <= 4294967296
content-type?string
Value in"application/octet-stream"
X-PAYMENT?string

Base64-encoded x402 payment authorization for pay-as-you-go USDC payments.

x402 Payment Flow:

  1. Upload without X-PAYMENT → Server returns 402 with payment requirements
  2. Client creates EIP-3009 authorization and signs with EIP-712
  3. Client retries upload with X-PAYMENT header containing base64(JSON)
  4. Server verifies signature, settles USDC transfer, processes upload
  5. Server returns receipt with x402Payment object and X-Payment-Response header

Decoded Structure (see X402PaymentHeader schema):

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x...(EIP-712 signature)...",
    "authorization": {
      "from": "0xPayerAddress",
      "to": "0xBundlerPaymentAddress",
      "value": "30490",
      "validAfter": 0,
      "validBefore": 1735689600,
      "nonce": "0x...(32 bytes hex)..."
    }
  }
}

Creating the Header:

const payload = { x402Version: 1, scheme: "exact", network: "base", payload: {...} };
const xPayment = Buffer.from(JSON.stringify(payload)).toString("base64");
headers["X-PAYMENT"] = xPayment;

Requirements:

  • Content-Length header MUST be present when using X-PAYMENT
  • Authorization value must match or exceed the 402 response's maxAmountRequired
  • Nonce must be unique (32-byte random hex string)

Supported Networks: base, base-sepolia, ethereum-mainnet, polygon-mainnet

Standards:

Formatbyte

A signed ANS-104 data item (binary)

bodyfile

ANS-104 data item signed with Arweave, Ethereum, or Solana key

Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/x402/data-item/signed" \  -H "content-length: 4294967296" \  -H "content-type: application/octet-stream" \  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHgxMjM0NTY3ODkwYWJjZGVmLi4uIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjBiRWIwIiwidG8iOiIweENGZDNmOTk2NDQ3YTU0MUNiZmJhNTQyMjMxMEVEYjQxN2Q5ZjJjRTYiLCJ2YWx1ZSI6IjMwNDkwIiwidmFsaWRBZnRlciI6MCwidmFsaWRCZWZvcmUiOjE3MzU2ODk2MDAsIm5vbmNlIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVmIn19fQ==" \  -H "Content-Type: application/octet-stream" \  -d 'string'
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "owner": "8wgRDgvYOrtSaWEIV21g0lTuWDUnTu4_iYj4hmA7PI0",
  "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "dataCaches": [
    "arweave.net"
  ],
  "fastFinalityIndexes": [
    "arweave.net"
  ],
  "deadlineHeight": 1310000,
  "timestamp": 1700590909589,
  "version": "0.1.0",
  "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQjKXuT4lTngRFBFKO5NQT1iIfqSDKcbTRL6gJowM_L7bBQZRGkojzXD0PNNU2F0bNNJ80VtUktHifGTXbCgz5kiFciL19n0P3nX6ZfXnOn-H8ALZzRJV69apdvwqitpNKxLMPyc-QA0QBxmC3CKPz_7fy2Qg0QHr5g_ZT2Of-YJ_RsZTEoc3g1fgzsEmMBPOsx4XtPrhV6llnA3pncngzHbPdFvypdWiO8Bvr0EWmazNsoanuwK5uKJ_ROIGXW_dBBGN8Vrfv5U6dJnhJVn5IE7JFlixpFTluF_ICRzbUq2pk_re6jEtW1H3ItH2iN0UeFUw1uDbq3HJW6lDc8aOwDwDspJI11KEI6uCz5QmQy2V8DvRknoqcxmuihF6XmmJIZgTVeo6LNufEis9kFxqtc3Dh_gn8z0cDXKEKFycudckmcHP7vkWD68uSssMMJIdVgwvPZss06svfRnI-E33j3MrQI9FzMIv-7Df8iYATyeyldM1v3gexG0kQm0AMG1_8_SLqwu2QlqzM41mrK5vNmQxOVdIQSOPWPvzbF-YGRwpCjlveRBuARGC9JNC4UipvDYri2gRWuBx2uDL7dmVFv1gRll3dYNMYaMULHYngtrrCynB3Cyfhh7cyPlwuNlk0",
  "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqptsCiqNOtB3RKUToSX5hkPD45fJDY4057XkkcsQRuGsU8y9rgm37i-Kiyd5Z_iy6pJXrwi9XnAgGL118lIV790GZ7xe5o3DvPV3Px74C0ABsfL9lW86D4t_qClJ6wSQksKNd7rnUImIvHW0vxLswST7dfUngevzKt4kv48VTub4951XdUHjb45Uurf7xFYSCizAGtGqr5GYDFrVk-mNrzFH5bXt06PJfxe9E5ujIE5Uq1Az6vqEOO0E1mWmXqdTPluAxcjmgktkoNLHZnnU-BsYuFaTWW5NU3aS-RgJKXYs9O6Dc1-2SITl-H_wtdGNSj31fj72UkaAbkpA1mionK-8bOIkSpYgKCyTC42oHh1Fw4SLXPyLxBj1w6F32LSLjpse5dmKymj4fJPezCMdi709uIiVT7XOm4LZBCzFOxS6-UNxgE57dBFpcWkcqNO3p00biYqH5d5bzvK3bwd-4j0KyKlqPYTProSnd3P6ROZuuJaLoLD_Or5-L_dUjawHz-DFlzmckYaf8l3XdzPM4JSsE8CEDZl0NCN0AidXt_wjbr6k9JsO7cnLB226AjxhcyuxZOlhgkIn7EbxpVXx-O2mXkcXF8PixCP0k5brtriLeF1MAdspgd_S-LKQeGVr7-mk",
  "x402Payment": {
    "paymentId": "550e8400-e29b-41d4-a716-446655440000",
    "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
    "network": "base-mainnet",
    "mode": "payg"
  }
}

"Data Item Exists"

"Invalid Content Type"

{
  "x402Version": 1,
  "accepts": [
    {}
  ],
  "error": "Payment required"
}
"Internal server error"

Posts raw data with x402 payment (bundler creates ANS-104 wrapper)

x402-Specific Upload Endpoint for Raw Data (Server-Signed)

This endpoint accepts raw data (images, files, text, etc.) and the bundler creates and signs the ANS-104 data item wrapper server-side. Payment is via x402 USDC.

Use Case: AI agents, simple apps, and clients that don't want to implement ANS-104 signing.


Payment Flow

1. POST raw data (no X-PAYMENT header)

2. Server returns 402 with payment requirements

3. Client creates EIP-3009 authorization + EIP-712 signature

4. POST raw data WITH X-PAYMENT header

5. Server: verify → settle USDC → create data item → store → return receipt

Pricing

Formula:

estimatedSize = rawDataSize + 512 (signature) + 512 (owner) + 80 (headers) + (totalTags × 64)
winstonCost = arweaveGateway.getPrice(estimatedSize)
usdcAmount = (winstonCost / 10^12) × arPriceUSD × 10^6 × (1 + feePercent/100)

Fee: Configurable via X402_PRICING_BUFFER_PERCENT (default: 15%)

Get price quote first: GET /v1/price/x402/data/{token}/{byteCount}?tags={n}&contentType={mime}


System Tags (Automatically Added)

TagDescriptionExample
BundlerService name"AR.IO Bundler"
Upload-TypeUpload method"raw-data-x402"
Payer-AddressEthereum payer"0x742d35Cc..."
X402-TX-HashBlockchain tx"0x9c31110e..."
X402-Payment-IDTracking UUID"edd4766e-..."
X402-NetworkPayment network"base"
Upload-TimestampUnix ms"1700590909589"
Content-TypeMIME type"image/png"

Custom Tags

Via Headers: X-Tag-App-Name: MyApp{"name": "App-Name", "value": "MyApp"}

Via JSON Envelope: {"data": "...", "tags": [{"name": "App-Name", "value": "MyApp"}]}

Note: Header tag names are converted from kebab-case to Proper-Case.


Important Notes

  • Ownership: The owner field is the bundler's server wallet. The payer's address is recorded in the Payer-Address tag only.
  • Server Requirement: Requires RAW_DATA_UPLOADS_ENABLED=true on server
  • Max Size: 10 GB
  • Max Tags: 100 user tags

Supported Networks

NetworkTokenStatusUSDC Contract
Base mainnetusdc-baseDefault0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Base Sepoliausdc-base-sepoliaRequires env0x036CbD53842c5426634e7929541eC2318f3dCF7e
Ethereumusdc-ethereum-mainnetRequires env0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Polygonusdc-polygon-mainnetRequires env0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
POST
/x402/upload/unsigned

Header Parameters

content-length?integer
Formatint64
Rangevalue <= 4294967296
Content-Type?string

MIME type of the raw data (e.g., image/png, application/pdf)

X-PAYMENT?string

Base64-encoded x402 payment authorization for pay-as-you-go USDC payments.

x402 Payment Flow:

  1. Upload without X-PAYMENT → Server returns 402 with payment requirements
  2. Client creates EIP-3009 authorization and signs with EIP-712
  3. Client retries upload with X-PAYMENT header containing base64(JSON)
  4. Server verifies signature, settles USDC transfer, processes upload
  5. Server returns receipt with x402Payment object and X-Payment-Response header

Decoded Structure (see X402PaymentHeader schema):

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x...(EIP-712 signature)...",
    "authorization": {
      "from": "0xPayerAddress",
      "to": "0xBundlerPaymentAddress",
      "value": "30490",
      "validAfter": 0,
      "validBefore": 1735689600,
      "nonce": "0x...(32 bytes hex)..."
    }
  }
}

Creating the Header:

const payload = { x402Version: 1, scheme: "exact", network: "base", payload: {...} };
const xPayment = Buffer.from(JSON.stringify(payload)).toString("base64");
headers["X-PAYMENT"] = xPayment;

Requirements:

  • Content-Length header MUST be present when using X-PAYMENT
  • Authorization value must match or exceed the 402 response's maxAmountRequired
  • Nonce must be unique (32-byte random hex string)

Supported Networks: base, base-sepolia, ethereum-mainnet, polygon-mainnet

Standards:

Formatbyte
X-TAG-*?string

Custom tags in the format X-TAG-Name: Value. Multiple tags can be specified. Tag names are case-insensitive.

Raw data to upload. Supports two formats:

1. Binary Upload (Recommended for files)

  • Content-Type: application/octet-stream, image/png, application/pdf, etc.
  • Body: Raw binary data
  • Custom tags via X-Tag-* headers

2. JSON Envelope (Alternative for programmatic use)

  • Content-Type: application/json
  • Body: {"data": "<base64>", "contentType": "...", "tags": [...]}
  • Useful when headers are difficult to set (e.g., some HTTP clients)
bodyfile

Raw binary data (use X-Tag-* headers for custom tags)

Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/x402/upload/unsigned" \  -H "content-length: 4294967296" \  -H "Content-Type: image/png" \  -H "X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHgxMjM0NTY3ODkwYWJjZGVmLi4uIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjBiRWIwIiwidG8iOiIweENGZDNmOTk2NDQ3YTU0MUNiZmJhNTQyMjMxMEVEYjQxN2Q5ZjJjRTYiLCJ2YWx1ZSI6IjMwNDkwIiwidmFsaWRBZnRlciI6MCwidmFsaWRCZWZvcmUiOjE3MzU2ODk2MDAsIm5vbmNlIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwYWJjZGVmIn19fQ==" \  -H "X-TAG-*: string" \  -H "Content-Type: application/json" \  -d '{    "data": "SGVsbG8gV29ybGQh",    "contentType": "text/plain"  }'

{
  "id": "rNephs2z5BAAUlmZm_TGwQjz4TiMr6HoZNyBO3eLctA",
  "owner": "jHGQATyLh_yBNKwqPmU-y6qV98q-AB48fIWUiFOSJAA",
  "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "dataCaches": [
    "arweave.net"
  ],
  "fastFinalityIndexes": [
    "arweave.net"
  ],
  "receipt": {
    "id": "rNephs2z5BAAUlmZm_TGwQjz4TiMr6HoZNyBO3eLctA",
    "timestamp": 1700590909589,
    "version": "0.2.0",
    "deadlineHeight": 1310000,
    "dataCaches": [
      "arweave.net"
    ],
    "fastFinalityIndexes": [
      "arweave.net"
    ],
    "winc": "1000000",
    "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqpts...",
    "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQj..."
  },
  "x402Payment": {
    "paymentId": "edd4766e-a053-47f2-88d1-819e72c534dd",
    "transactionHash": "0x9c31110e240805d9dfa5853c661f23a258c89f78b8d2e4424a922f9dfd8748f3",
    "network": "base",
    "mode": "payg"
  }
}

"Invalid data. Data size exceeds maximum allowed size of 4294967296 bytes (4 GiB)"

{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "3508",
      "resource": "https://upload.yourdomain.com/v1/x402/upload/unsigned",
      "description": "Upload data to Arweave via AR.IO Bundler",
      "mimeType": "application/json",
      "payTo": "0xCFd3f996447a541Cbfba5422310EDb417d9f2cE6",
      "maxTimeoutSeconds": 3600,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    }
  ]
}
"Raw data uploads are not enabled on this bundler"
"Upload Service is Unavailable. Payment Service is unreachable"

Creates a new multi-part upload. Chunks should be posted to /chunks/:token/:uploadId/:chunkOffset using the returned uploadId. ARx compatible.

GET
/chunks/{token}/-1/-1

Path Parameters

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"

Response Body

curl -X GET "https://turbo.ardrive.io/chunks/arweave/-1/-1"
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "min": 2500,
  "max": "500_000_000"
}
"Internal server error"

Gets an existing multi-part upload, including all existing chunks that have been uploaded. ARx compatible.

GET
/chunks/:token/:uploadId/-1

Path Parameters

uploadIdstring

The upload id of the multi-part upload.

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"

Response Body

curl -X GET "https://turbo.ardrive.io/chunks/:token/:uploadId/-1"
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "min": 2500,
  "max": 500000000,
  "size": 25000000,
  "chunks": [
    [
      25000000,
      25000000
    ],
    [
      50000000,
      25000000
    ],
    [
      75000000,
      25000000
    ],
    [
      100000000,
      4858676
    ]
  ]
}

"Multi-part upload not found"
"Internal server error"

Finalizes a multi-part upload. ARx compatible.

POST
/chunks/:token/:uploadId/-1

Path Parameters

uploadIdstring

The upload id of the multi-part upload.

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"

Response Body

curl -X POST "https://turbo.ardrive.io/chunks/:token/:uploadId/-1"
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "data": {
    "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
    "owner": "8wgRDgvYOrtSaWEIV21g0lTuWDUnTu4_iYj4hmA7PI0",
    "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
    "dataCaches": [
      "arweave.net"
    ],
    "fastFinalityIndexes": [
      "arweave.net"
    ],
    "deadlineHeight": 1310000,
    "timestamp": 1700590909589,
    "version": "0.1.0",
    "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQjKXuT4lTngRFBFKO5NQT1iIfqSDKcbTRL6gJowM_L7bBQZRGkojzXD0PNNU2F0bNNJ80VtUktHifGTXbCgz5kiFciL19n0P3nX6ZfXnOn-H8ALZzRJV69apdvwqitpNKxLMPyc-QA0QBxmC3CKPz_7fy2Qg0QHr5g_ZT2Of-YJ_RsZTEoc3g1fgzsEmMBPOsx4XtPrhV6llnA3pncngzHbPdFvypdWiO8Bvr0EWmazNsoanuwK5uKJ_ROIGXW_dBBGN8Vrfv5U6dJnhJVn5IE7JFlixpFTluF_ICRzbUq2pk_re6jEtW1H3ItH2iN0UeFUw1uDbq3HJW6lDc8aOwDwDspJI11KEI6uCz5QmQy2V8DvRknoqcxmuihF6XmmJIZgTVeo6LNufEis9kFxqtc3Dh_gn8z0cDXKEKFycudckmcHP7vkWD68uSssMMJIdVgwvPZss06svfRnI-E33j3MrQI9FzMIv-7Df8iYATyeyldM1v3gexG0kQm0AMG1_8_SLqwu2QlqzM41mrK5vNmQxOVdIQSOPWPvzbF-YGRwpCjlveRBuARGC9JNC4UipvDYri2gRWuBx2uDL7dmVFv1gRll3dYNMYaMULHYngtrrCynB3Cyfhh7cyPlwuNlk0",
    "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqptsCiqNOtB3RKUToSX5hkPD45fJDY4057XkkcsQRuGsU8y9rgm37i-Kiyd5Z_iy6pJXrwi9XnAgGL118lIV790GZ7xe5o3DvPV3Px74C0ABsfL9lW86D4t_qClJ6wSQksKNd7rnUImIvHW0vxLswST7dfUngevzKt4kv48VTub4951XdUHjb45Uurf7xFYSCizAGtGqr5GYDFrVk-mNrzFH5bXt06PJfxe9E5ujIE5Uq1Az6vqEOO0E1mWmXqdTPluAxcjmgktkoNLHZnnU-BsYuFaTWW5NU3aS-RgJKXYs9O6Dc1-2SITl-H_wtdGNSj31fj72UkaAbkpA1mionK-8bOIkSpYgKCyTC42oHh1Fw4SLXPyLxBj1w6F32LSLjpse5dmKymj4fJPezCMdi709uIiVT7XOm4LZBCzFOxS6-UNxgE57dBFpcWkcqNO3p00biYqH5d5bzvK3bwd-4j0KyKlqPYTProSnd3P6ROZuuJaLoLD_Or5-L_dUjawHz-DFlzmckYaf8l3XdzPM4JSsE8CEDZl0NCN0AidXt_wjbr6k9JsO7cnLB226AjxhcyuxZOlhgkIn7EbxpVXx-O2mXkcXF8PixCP0k5brtriLeF1MAdspgd_S-LKQeGVr7-mk",
    "x402Payment": {
      "paymentId": "550e8400-e29b-41d4-a716-446655440000",
      "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
      "network": "base-mainnet",
      "mode": "payg"
    }
  }
}
"Insufficient balance"

"Multi-part upload not found"
"Internal server error"

Finalizes a multi-part upload asynchronously.

POST
/chunks/:token/:uploadId/finalize

Path Parameters

uploadIdstring

The upload id of the multi-part upload.

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"

Response Body

curl -X POST "https://turbo.ardrive.io/chunks/:token/:uploadId/finalize"
{
  "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
  "data": {
    "id": "QpmY8mZmFEC8RxNsgbxSV6e36OF6quIYaPRKzvUco0o",
    "owner": "8wgRDgvYOrtSaWEIV21g0lTuWDUnTu4_iYj4hmA7PI0",
    "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
    "dataCaches": [
      "arweave.net"
    ],
    "fastFinalityIndexes": [
      "arweave.net"
    ],
    "deadlineHeight": 1310000,
    "timestamp": 1700590909589,
    "version": "0.1.0",
    "signature": "iU_S6uuG1OD8k0XqMGOmbcKfysDckEMUy4R9-ODPXiQjKXuT4lTngRFBFKO5NQT1iIfqSDKcbTRL6gJowM_L7bBQZRGkojzXD0PNNU2F0bNNJ80VtUktHifGTXbCgz5kiFciL19n0P3nX6ZfXnOn-H8ALZzRJV69apdvwqitpNKxLMPyc-QA0QBxmC3CKPz_7fy2Qg0QHr5g_ZT2Of-YJ_RsZTEoc3g1fgzsEmMBPOsx4XtPrhV6llnA3pncngzHbPdFvypdWiO8Bvr0EWmazNsoanuwK5uKJ_ROIGXW_dBBGN8Vrfv5U6dJnhJVn5IE7JFlixpFTluF_ICRzbUq2pk_re6jEtW1H3ItH2iN0UeFUw1uDbq3HJW6lDc8aOwDwDspJI11KEI6uCz5QmQy2V8DvRknoqcxmuihF6XmmJIZgTVeo6LNufEis9kFxqtc3Dh_gn8z0cDXKEKFycudckmcHP7vkWD68uSssMMJIdVgwvPZss06svfRnI-E33j3MrQI9FzMIv-7Df8iYATyeyldM1v3gexG0kQm0AMG1_8_SLqwu2QlqzM41mrK5vNmQxOVdIQSOPWPvzbF-YGRwpCjlveRBuARGC9JNC4UipvDYri2gRWuBx2uDL7dmVFv1gRll3dYNMYaMULHYngtrrCynB3Cyfhh7cyPlwuNlk0",
    "public": "qREovbmD6oxgHYNCzOeTei07lSz0-YLcjnvgSDzqptsCiqNOtB3RKUToSX5hkPD45fJDY4057XkkcsQRuGsU8y9rgm37i-Kiyd5Z_iy6pJXrwi9XnAgGL118lIV790GZ7xe5o3DvPV3Px74C0ABsfL9lW86D4t_qClJ6wSQksKNd7rnUImIvHW0vxLswST7dfUngevzKt4kv48VTub4951XdUHjb45Uurf7xFYSCizAGtGqr5GYDFrVk-mNrzFH5bXt06PJfxe9E5ujIE5Uq1Az6vqEOO0E1mWmXqdTPluAxcjmgktkoNLHZnnU-BsYuFaTWW5NU3aS-RgJKXYs9O6Dc1-2SITl-H_wtdGNSj31fj72UkaAbkpA1mionK-8bOIkSpYgKCyTC42oHh1Fw4SLXPyLxBj1w6F32LSLjpse5dmKymj4fJPezCMdi709uIiVT7XOm4LZBCzFOxS6-UNxgE57dBFpcWkcqNO3p00biYqH5d5bzvK3bwd-4j0KyKlqPYTProSnd3P6ROZuuJaLoLD_Or5-L_dUjawHz-DFlzmckYaf8l3XdzPM4JSsE8CEDZl0NCN0AidXt_wjbr6k9JsO7cnLB226AjxhcyuxZOlhgkIn7EbxpVXx-O2mXkcXF8PixCP0k5brtriLeF1MAdspgd_S-LKQeGVr7-mk",
    "x402Payment": {
      "paymentId": "550e8400-e29b-41d4-a716-446655440000",
      "transactionHash": "0xb8a52725cdc816d060392dcecaa1528872acf9ea0bd097a86e2432dd5d2213ca",
      "network": "base-mainnet",
      "mode": "payg"
    }
  }
}
"Insufficient balance"

"Multi-part upload not found"
"Internal server error"

Gets the status of a multi-part upload.

GET
/chunks/:token/:uploadId/status

Path Parameters

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"
uploadIdstring

The upload id of the multi-part upload.

Response Body

curl -X GET "https://turbo.ardrive.io/chunks/:token/:uploadId/status"
{
  "status": "VALIDATING",
  "timestamp": 1700590909589
}

"Multi-part upload not found"
"Internal server error"

Posts a chunk of a multi-part upload. ARx compatible.

POST
/chunks/:token/:uploadId/:chunkOffset

Path Parameters

uploadIdstring

The upload id of the multi-part upload.

tokenstring

The token to use for validating the transaction.

Value in"arweave" | "ethereum" | "solana"
chunkOffsetinteger

The offset of the chunk in bytes. If -1, that will finalize an existing multi-part upload.

A chunk of a multi-part upload

bodyfile
Formatbinary

Response Body

curl -X POST "https://turbo.ardrive.io/chunks/:token/:uploadId/:chunkOffset" \  -H "Content-Type: application/octet-stream" \  -d 'string'
Empty
"Insufficient balance"

"Multi-part upload not found"
"Internal server error"

How is this guide?