> x402
Base の USDC で従量払い
概要
JSONBill は x402 v2 によるオンチェーンの従量払い(USDC)に対応しています。これは Quaily クレジット以外の支払い方法です。
注意:x402 で支払う場合、Authorization ヘッダーは不要です。
対応ネットワーク/資産: Base(eip155:8453)USDC。
x402 は AI エージェントやプログラム向けの支払いを想定したオープンなプロトコルで、HTTP 402 を使って従量課金を実現します。Coinbase が主導し、Coinbase と Cloudflare が x402 を立ち上げ、オープンなインターネット標準としての推進を目指しています。
前提条件
- Base のウォレットに十分な USDC を用意。
- 秘密鍵を
X402_PRIVATE_KEYに設定(クライアント SDK が使用)。
Step 1: リクエスト JSON を用意
最小のリクエスト例(API ドキュメントと同じ構造):
{
"type": "invoice",
"lang": "ja",
"theme": "classic",
"params": {
"id": "demo-001",
"date": "2026-01-27T00:00:00Z",
"currency": "USD",
"company_name": "Demo Co",
"bill_to_company": "Client",
"doc": {
"title": "Invoice",
"description": "Demo"
},
"summary": {
"title": "Service",
"total_include_tax": 1
},
"detail_items": [
{
"title": "Item",
"desc": "Work",
"total_include_tax": 1
}
]
}
}
精算書の場合: "type" を "settlementstatement" にし、recipient_company / recipient_address を使ってください。payment.instruction は無視され、payment.result のみ表示されます。
Step 2: 未払いリクエストを送信
curl -i -X POST https://api.jsonbill.com/tasks/docs \
-H 'Content-Type: application/json' \
-d @doc.json
402 Payment Required が返り、レスポンスヘッダーに PAYMENT-REQUIRED(base64 の PaymentRequired JSON)が含まれます。
Step 3: x402 SDK で署名して再送
Go(x402 SDK)
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
x402sdk "github.com/coinbase/x402/go"
x402http "github.com/coinbase/x402/go/http"
evmscheme "github.com/coinbase/x402/go/mechanisms/evm/exact/client"
evmsigner "github.com/coinbase/x402/go/signers/evm"
)
func main() {
pk := os.Getenv("X402_PRIVATE_KEY")
signer, _ := evmsigner.NewClientSignerFromPrivateKey(pk)
x402Client := x402sdk.Newx402Client()
x402Client.Register(x402sdk.Network("eip155:*"), evmscheme.NewExactEvmScheme(signer))
x402HTTP := x402http.NewClient(x402Client)
body, _ := os.ReadFile("invoice.json")
httpClient := &http.Client{Timeout: 30 * time.Second}
req, _ := http.NewRequest("POST", "https://api.jsonbill.com/tasks/docs", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := httpClient.Do(req)
respBody, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusPaymentRequired {
fmt.Printf("status: %s\nbody:\n%s\n", resp.Status, string(respBody))
return
}
headers := map[string]string{}
for k, v := range resp.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
required, _ := x402HTTP.GetPaymentRequiredResponse(headers, respBody)
selected, _ := x402Client.SelectPaymentRequirements(required.Accepts)
payload, _ := x402Client.CreatePaymentPayload(context.Background(), selected, required.Resource, required.Extensions)
payloadBytes, _ := json.Marshal(payload)
paymentHeaders := x402HTTP.EncodePaymentSignatureHeader(payloadBytes)
payReq, _ := http.NewRequest("POST", "https://api.jsonbill.com/tasks/docs", bytes.NewReader(body))
payReq.Header.Set("Content-Type", "application/json")
for k, v := range paymentHeaders {
payReq.Header.Set(k, v)
}
payResp, _ := httpClient.Do(payReq)
defer payResp.Body.Close()
payBody, _ := io.ReadAll(payResp.Body)
fmt.Printf("status: %s\nbody:\n%s\n", payResp.Status, string(payBody))
}
TypeScript(x402 SDK)
import fs from "node:fs";
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
import { createWalletClient, http } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
const pk = process.env.X402_PRIVATE_KEY!;
const account = privateKeyToAccount((pk.startsWith("0x") ? pk : `0x${pk}`) as `0x${string}`);
const walletClient = createWalletClient({ chain: base, transport: http(), account });
const signer = toClientEvmSigner({
address: account.address,
signTypedData: (args) =>
walletClient.signTypedData({
account,
domain: args.domain,
types: args.types,
primaryType: args.primaryType,
message: args.message,
}),
});
const client = new x402Client().register(
`eip155:${base.id}`,
new ExactEvmScheme(signer)
);
const x402Fetch = wrapFetchWithPayment(fetch, client);
const payload = JSON.parse(fs.readFileSync("invoice.json", "utf-8"));
const resp = await x402Fetch("https://api.jsonbill.com/tasks/docs", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
const text = await resp.text();
console.log(`status: ${resp.status}`);
if (text) {
try {
console.log(JSON.stringify(JSON.parse(text), null, 2));
} catch {
console.log(text);
}
}
Step 4: ステータス確認 / ダウンロード
レスポンスには trace_id が含まれます。短期限の download_url と download_token が返され、直接 PDF を取得できます(有効期限 15 分)。
レスポンス例:
{
"task": {
"id": 1,
"trace_id": "f4730554-791c-463b-9a27-aeacdfb02cf2",
"status": 0,
// ...
},
"download_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"download_token_expires_at": "2026-01-28T09:15:00Z",
"download_url": "https://api.jsonbill.com/tasks/f4730554-791c-463b-9a27-aeacdfb02cf2.pdf?download_token=..."
}
Notes
- 金額、資産、タイムアウトは
PAYMENT-REQUIREDに含まれます。 - x402 使用時は JWT/API key の認証ヘッダー不要です。