x402 Payments
Inkd uses x402 — an open HTTP payment protocol built on the 402 Payment Required status code. Agents and developers pay for API calls directly with USDC. No API keys, no OAuth, no accounts.
How it works
Request without payment
Your client calls a paid endpoint. The server responds with 402 Payment Required and payment instructions.
POST /v1/projects HTTP/1.1
Content-Type: application/json
{ "name": "my-agent", ... }HTTP/1.1 402 Payment Required
X-Payment-Required: {"amount":"100000","token":"USDC","network":"base"}Sign the authorization
Your client signs an EIP-3009 transferWithAuthorization — a gasless USDC transfer from your wallet to the Treasury contract.
const authorization = await signTransferAuthorization({
from: yourWallet,
to: TREASURY_ADDRESS,
value: 100000n, // 0.10 USDC (6 decimals)
validAfter: 0n,
validBefore: BigInt(Math.floor(Date.now() / 1000) + 300),
nonce: randomBytes32(),
})Retry with payment header
The client retries the request with the signed authorization in the X-PAYMENT header.
POST /v1/projects HTTP/1.1
X-PAYMENT: <base64-encoded EIP-3009 authorization>
Content-Type: application/json
{ "name": "my-agent", ... }Server executes and responds
The server verifies the signature, executes the USDC transfer on-chain, and processes the request.
HTTP/1.1 201 Created
{ "projectId": "10", "txHash": "0xabc...", ... }Why x402?
For AI agents
Agents hold wallets. x402 lets them pay directly — no human needs to create an API key, renew a subscription, or handle rate limits. A wallet with USDC is all that's required.
// No configuration. Just a wallet.
const client = new ProjectsClient({ privateKey: process.env.WALLET_KEY })
await client.createProject({ name: "my-agent", ... })For developers
x402 is standard HTTP. Any language that can make HTTP requests and sign EIP-3009 authorizations can use the Inkd API.
EIP-3009
Inkd uses EIP-3009 transferWithAuthorization, which is native to USDC on Base.
Key properties:
- Gasless for the payer — the API server submits the transaction and pays gas
- Atomic — payment and API execution happen in the same server-side flow
- Replay-safe — each authorization has a unique nonce and expiry
USDC EIP-712 domain on Base:
name: "USD Coin"
version: "2"
chainId: 8453
verifyingContract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Pricing
Prices are dynamic — charged based on actual resource cost plus a 20% protocol markup.
| Operation | Cost |
|---|---|
| List projects | Free |
| Get project | Free |
| List versions | Free |
| Create project | $0.10 USDC (min) |
| Push version | Arweave storage + 20% markup (min $0.10) |
| Upload to Arweave | Free (cost included in version push fee) |
For version pushes, the exact fee depends on file size. Use GET /v1/projects/estimate?bytes=N to get a price estimate before paying.
Revenue flow
Every payment flows through the protocol:
Agent pays USDC
│
▼
InkdTreasury.settle(total, arweaveCost)
│
├── arweaveCost ──────────────► arweaveWallet
│ (covers Arweave storage)
│
└── markup (20%)
│
├── 50% ──────────────► InkdBuyback
│ (auto-buys $INKD at $50 threshold)
│
└── 50% ──────────────► Treasury SafeUsing x402 in your app
With the Inkd SDK (recommended)
The SDK handles x402 automatically:
import { ProjectsClient } from "@inkd/sdk"
const client = new ProjectsClient({
privateKey: process.env.INKD_PRIVATE_KEY,
})
// Payment happens automatically
const project = await client.createProject({ name: "my-agent" })With @x402/fetch
npm install @x402/fetch @x402/evm viemimport { wrapFetchWithPayment } from "@x402/fetch"
import { viemAdapter } from "@x402/evm"
import { createWalletClient, http } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { base } from "viem/chains"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const wallet = createWalletClient({ account, chain: base, transport: http() })
const fetchWithPayment = wrapFetchWithPayment(fetch, viemAdapter(wallet))
const res = await fetchWithPayment("https://api.inkdprotocol.com/v1/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "my-agent", description: "...", license: "MIT", isPublic: true }),
})Raw (any language)
- Make the initial request (no auth)
- Parse the
402response to get payment amount and recipient - Sign EIP-3009
transferWithAuthorizationwith your wallet - Base64-encode the signed payload
- Retry with
X-PAYMENT: <encoded>header
