Skip to content

API Reference

HTTP API for the x402 IPFS Gateway. All endpoints documented in OpenAPI 3.0.

Network

  • Base Mainnet: Production gateway with payment required
  • Base Sepolia: Testnet gateway (use for development)

Endpoints

All endpoints are served from the gateway subdomain in production or localhost in development:

  • Production: https://gateway.offchain.wtf
  • API (no payment): https://api.offchain.wtf
  • Development: http://localhost:3000

IPFS Gateway Endpoints

GET /ipfs/

Retrieve an IPFS file or directory by CID.

Parameters:
  • cid (path, required): IPFS Content Identifier (v0 or v1)
  • * (path, optional): Nested path within directory
  • download (query, optional): Force file download instead of inline display
Request Headers:
  • X-PAYMENT (optional): x402 signed payment proof for retry requests
  • Origin (optional): CORS origin validation
  • CF-Connecting-IP (optional): Cloudflare IP for rate limiting
  • X-Forwarded-For (optional): Proxy IP for rate limiting
Success Response (200):

For files:

Content-Type: <detected-mime-type>
Content-Length: <size-in-bytes>
Cache-Control: public, max-age=31536000, immutable
X-IPFS-Path: /ipfs/{cid}/{path}
 
<raw binary file content>

For directories (when index.html not found or disabled):

{
  "type": "directory",
  "entries": [
    {
      "name": "file.txt",
      "type": "file",
      "size": 1024,
      "cid": "QmHash..."
    },
    {
      "name": "subdir",
      "type": "directory",
      "size": 0,
      "cid": "QmHash..."
    }
  ]
}

For directory with index.html (when ALLOW_INDEX_HTML=true):

Content-Type: text/html
Content-Length: <size-in-bytes>
Cache-Control: public, max-age=31536000, immutable
X-IPFS-Path: /ipfs/{cid}/{path}/index.html
 
<html content>
Payment Required Response (402):

When payment is required, the gateway returns 402 with payment instructions:

{
  "error": "Payment required",
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "10000",
      "resource": "http://localhost:3000/ipfs/QmHash",
      "description": "Access IPFS content via gateway",
      "mimeType": "application/octet-stream",
      "payTo": "0x786A0Db0C2110951BAF55e419B57412eD29BA1B0",
      "maxTimeoutSeconds": 120,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "outputSchema": {
        "input": {
          "type": "http",
          "method": "GET",
          "discoverable": true
        }
      },
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ],
  "x402Version": 1
}
Error Responses:
StatusSchemaDescription
404{"error": "Path not found: filename"}Path doesn't exist in IPFS
403{"error": "HTML serving is disabled. Set ALLOW_INDEX_HTML=true to enable."}HTML file blocked
429{"error": "Rate limit exceeded", "retryAfter": 30}Too many requests from IP
500{"error": "<error message>"}Server error
Rate Limit Headers:

All responses include rate limit information:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1700000000

On 429 responses:

Retry-After: 30

GET /ipfs/
/*

Retrieve nested IPFS content. Same as /ipfs/:cid but with path traversal.

Example:
GET /ipfs/QmParentDir/subfolder/file.txt

x402 Payment Endpoint

POST /api/x402-proxy

Internal endpoint for x402 payment verification. Used by SDK internally to handle CORS issues.

Request Body:
{
  "url": "http://localhost:3000/ipfs/QmHash",
  "headers": {
    "X-Custom-Header": "value"
  }
}
Success Response (200):
{
  "status": 200,
  "body": "<file content or JSON>",
  "contentType": "application/octet-stream"
}
Error Responses:
StatusCodeDescription
400MISSING_URLNo URL provided
400INVALID_URLInvalid URL format
500PROXY_ERRORProxy request failed

Subdomain Routing

The gateway supports different behavior based on subdomain:

SubdomainPaymentUse Case
api.offchain.wtf❌ NoServer-to-server API access
gateway.offchain.wtf✅ YesUser-facing gateway with payment
www.offchain.wtf✅ YesWeb application (served from gateway)
offchain.wtf (root)✅ YesRoot domain with payment
localhost:3000✅ YesDevelopment (default to root)

Configuration

Environment Variables

# x402 Payment
PAYMENT_RECIPIENT=0x786A0Db0C2110951BAF55e419B57412eD29BA1B0
PAYMENT_PRICE=$0.01
PAYMENT_NETWORK=base              # or base-sepolia

# Gateway Features
ALLOW_INDEX_HTML=true
RATE_LIMIT_REQUESTS=10
RATE_LIMIT_WINDOW=60

# Optional Security
IP_ALLOWLIST=1.2.3.4,5.6.7.8
DOMAIN_ALLOWLIST=example.com,*.example.org

# CDP Credentials (for mainnet)
CDP_API_KEY_ID=your_key_id
CDP_API_PRIVATE_KEY=your_private_key

Wrangler Configuration

Same variables in wrangler.jsonc:

{
  "vars": {
    "PAYMENT_RECIPIENT": "0x786A0Db0C2110951BAF55e419B57412eD29BA1B0",
    "PAYMENT_PRICE": "$0.01",
    "PAYMENT_NETWORK": "base",
    "ALLOW_INDEX_HTML": "true",
    "RATE_LIMIT_REQUESTS": "10",
    "RATE_LIMIT_WINDOW": "60"
  }
}

Response Headers

Standard Response Headers

Cache-Control: public, max-age=31536000, immutable
X-IPFS-Path: /ipfs/{cid}/{path}

CORS Headers (Development)

Access-Control-Allow-Origin: <origin>
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-PAYMENT
Access-Control-Max-Age: 3600

Rate Limiting Headers

X-RateLimit-Limit: <max-requests>
X-RateLimit-Remaining: <remaining-requests>
X-RateLimit-Reset: <unix-timestamp>

MIME Type Detection

The gateway automatically detects MIME types based on file extensions and content:

.html    → text/html
.json    → application/json
.jpg     → image/jpeg
.png     → image/png
.pdf     → application/pdf
.mp4     → video/mp4
...and 100+ more

Security Features

Rate Limiting

  • Per-IP: 10 requests per 60 seconds (configurable)
  • Bypass: API subdomain (api.offchain.wtf) not rate limited

CORS

  • Allows localhost and 127.0.0.1 in development
  • Allows offchain.wtf subdomains in production
  • Denies other origins

Optional IP Allowlist

Restrict access to specific IP addresses:

IP_ALLOWLIST=203.0.113.1,198.51.100.2

Optional Domain Allowlist

Restrict CORS origin with wildcard support:

DOMAIN_ALLOWLIST=example.com,*.example.org

Examples

Fetch a file

curl https://gateway.offchain.wtf/ipfs/QmHash

Force download

curl https://gateway.offchain.wtf/ipfs/QmHash?download

Nested path

curl https://gateway.offchain.wtf/ipfs/QmParentDir/subfolder/file.txt

With payment (using x402 SDK)

import { OffchainClient } from '@offchain-wtf/sdk';
 
const client = new OffchainClient({
  gatewayUrl: 'https://gateway.offchain.wtf',
  privateKey: process.env.PRIVATE_KEY
});
 
const file = await client.get('QmHash');

Interactive Exploration

Use these tools to explore the API:

Swagger UI - Interactive endpoint testing

https://editor.swagger.io/?url=https://gateway.offchain.wtf/docs/openapi

ReDoc - Formatted API documentation

https://redocly.github.io/redoc/?url=https://gateway.offchain.wtf/docs/openapi
Local OpenAPI JSON
http://localhost:3000/docs/openapi

See Also