AIコーディング 2026.04.20

Claude API月2ドルで使う方法|自作プロキシサーバーとcurlコマンドのセットアップ

タグ:Claude / API / 個人開発 / コスト削減 / curl

なぜプロキシサーバーを挟むのか

❌ 直接APIを呼ぶ場合:
  クライアント → Anthropic API
  問題: APIキーがフロントに露出・レート制限の制御が難しい

✅ プロキシを挟む場合:
  クライアント → 自作プロキシ($2/月) → Anthropic API
  メリット:
  - APIキーをサーバーに隠せる(クライアントには渡さない)
  - 自前のレート制限・ログ・コスト追跡ができる
  - 将来的にモデルを切り替えてもクライアント変更不要

Step 1:FastAPIでプロキシサーバーを実装

# proxy_server.py
# pip install fastapi uvicorn anthropic python-dotenv

import os
import time
from collections import defaultdict
from fastapi import FastAPI, HTTPException, Header
from fastapi.middleware.cors import CORSMiddleware
import anthropic
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# CORS設定(フロントエンドからのアクセスを許可)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # 本番では自分のドメインに変更
    allow_methods=["POST"],
    allow_headers=["*"],
)

# Anthropicクライアント(環境変数からAPIキーを取得)
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

# 簡易レート制限(本番ではRedisを使う)
request_counts: dict[str, list[float]] = defaultdict(list)
RATE_LIMIT = 10  # 1分間に最大10リクエスト

class ChatRequest(BaseModel):
    messages: list[dict]
    model: str = "claude-haiku-4-5-20251001"
    max_tokens: int = 1024
    system: Optional[str] = None

def check_rate_limit(client_ip: str) -> None:
    now = time.time()
    # 1分以内のリクエストだけ残す
    request_counts[client_ip] = [
        t for t in request_counts[client_ip] if now - t < 60
    ]
    if len(request_counts[client_ip]) >= RATE_LIMIT:
        raise HTTPException(
            status_code=429,
            detail=f"レート制限: 1分間に{RATE_LIMIT}回まで"
        )
    request_counts[client_ip].append(now)

@app.post("/v1/messages")
async def proxy_messages(
    request: ChatRequest,
    x_forwarded_for: str = Header(default="unknown")
):
    check_rate_limit(x_forwarded_for)
    
    try:
        kwargs = {
            "model": request.model,
            "max_tokens": request.max_tokens,
            "messages": request.messages,
        }
        if request.system:
            kwargs["system"] = request.system
        
        response = client.messages.create(**kwargs)
        
        return {
            "content": [{"type": "text", "text": response.content[0].text}],
            "usage": {
                "input_tokens": response.usage.input_tokens,
                "output_tokens": response.usage.output_tokens,
            }
        }
    
    except anthropic.RateLimitError:
        raise HTTPException(status_code=429, detail="Anthropic レート制限超過")
    except anthropic.AuthenticationError:
        raise HTTPException(status_code=401, detail="APIキーエラー")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Step 2:Fly.ioにデプロイ

# Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY proxy_server.py .

CMD ["uvicorn", "proxy_server:app", "--host", "0.0.0.0", "--port", "8080"]
# requirements.txt
fastapi==0.115.0
uvicorn==0.30.0
anthropic==0.98.1
python-dotenv==1.0.0
# Fly.ioにデプロイ(初回のみ fly auth login が必要)
fly launch --name my-claude-proxy

# APIキーをシークレットとして設定(コードには書かない)
fly secrets set ANTHROPIC_API_KEY=sk-ant-api03-xxxxx

# デプロイ
fly deploy

デプロイ後のURL: https://my-claude-proxy.fly.dev

最小インスタンス(shared-cpu-1x, 256MB)で月$2〜$3程度。

Step 3:curlでテスト

# 基本リクエスト
curl -X POST https://my-claude-proxy.fly.dev/v1/messages \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5-20251001",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "こんにちは"}]
  }'

# システムプロンプト付き
curl -X POST https://my-claude-proxy.fly.dev/v1/messages \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "system": "あなたは日本語のコードレビュアーです。",
    "messages": [{"role": "user", "content": "このコードをレビューして: def f(x): return x*x"}]
  }'

Pythonからプロキシ経由で呼ぶ

# call_proxy.py
import requests
import os

PROXY_URL = os.environ.get("CLAUDE_PROXY_URL", "http://localhost:8000")

def chat_via_proxy(user_message: str, system: str = "") -> str:
    payload = {
        "model": "claude-haiku-4-5-20251001",
        "max_tokens": 1024,
        "messages": [{"role": "user", "content": user_message}],
    }
    if system:
        payload["system"] = system
    
    response = requests.post(
        f"{PROXY_URL}/v1/messages",
        json=payload,
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    return data["content"][0]["text"]

# 使用例
result = chat_via_proxy(
    user_message="Pythonのdataclassを説明して",
    system="簡潔に3行以内で答えてください"
)
print(result)

コスト追跡を追加する

# proxy_server.pyに追加: コスト追跡ミドルウェア
from datetime import datetime
import json

COST_LOG_FILE = "/tmp/api_costs.jsonl"

# Haiku料金
HAIKU_INPUT_COST = 0.80 / 1_000_000
HAIKU_OUTPUT_COST = 4.00 / 1_000_000

def log_usage(input_tokens: int, output_tokens: int, model: str) -> float:
    cost = input_tokens * HAIKU_INPUT_COST + output_tokens * HAIKU_OUTPUT_COST
    
    with open(COST_LOG_FILE, "a") as f:
        f.write(json.dumps({
            "ts": datetime.now().isoformat(),
            "model": model,
            "input": input_tokens,
            "output": output_tokens,
            "cost_usd": cost,
        }) + "\n")
    
    return cost

# エンドポイント内で呼ぶ
# cost = log_usage(response.usage.input_tokens, response.usage.output_tokens, request.model)

あわせて読みたい

参考ソース