API Reference
HTTP API for the x402 IPFS Gateway. All endpoints documented in OpenAPI 3.0.
- OpenAPI JSON:
https://gateway.offchain.wtf/docs/openapi(orhttp://localhost:3000/docs/openapi) - Swagger UI: editor.swagger.io
- ReDoc: redocly.github.io
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 directorydownload(query, optional): Force file download instead of inline display
X-PAYMENT(optional): x402 signed payment proof for retry requestsOrigin(optional): CORS origin validationCF-Connecting-IP(optional): Cloudflare IP for rate limitingX-Forwarded-For(optional): Proxy IP for rate limiting
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>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
}| Status | Schema | Description |
|---|---|---|
| 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 |
All responses include rate limit information:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1700000000On 429 responses:
Retry-After: 30GET /ipfs//*
Retrieve nested IPFS content. Same as /ipfs/:cid but with path traversal.
GET /ipfs/QmParentDir/subfolder/file.txtx402 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"
}
}{
"status": 200,
"body": "<file content or JSON>",
"contentType": "application/octet-stream"
}| Status | Code | Description |
|---|---|---|
| 400 | MISSING_URL | No URL provided |
| 400 | INVALID_URL | Invalid URL format |
| 500 | PROXY_ERROR | Proxy request failed |
Subdomain Routing
The gateway supports different behavior based on subdomain:
| Subdomain | Payment | Use Case |
|---|---|---|
api.offchain.wtf | ❌ No | Server-to-server API access |
gateway.offchain.wtf | ✅ Yes | User-facing gateway with payment |
www.offchain.wtf | ✅ Yes | Web application (served from gateway) |
offchain.wtf (root) | ✅ Yes | Root domain with payment |
localhost:3000 | ✅ Yes | Development (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: 3600Rate 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+ moreSecurity Features
Rate Limiting
- Per-IP: 10 requests per 60 seconds (configurable)
- Bypass: API subdomain (
api.offchain.wtf) not rate limited
CORS
- Allows
localhostand127.0.0.1in development - Allows
offchain.wtfsubdomains 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/QmHashForce download
curl https://gateway.offchain.wtf/ipfs/QmHash?downloadNested path
curl https://gateway.offchain.wtf/ipfs/QmParentDir/subfolder/file.txtWith 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/openapiReDoc - Formatted API documentation
https://redocly.github.io/redoc/?url=https://gateway.offchain.wtf/docs/openapihttp://localhost:3000/docs/openapi