「API連携のコード、毎回ゼロから書いていませんか?」
「外部APIと連携する機能を作りたいけど、認証処理やエラーハンドリングを毎回書くのが面倒」「データベースとAPIをつなぐ処理、もっと効率化できないか」——開発現場でこうした声は後を絶ちません。
API連携は現代のソフトウェア開発において避けて通れない領域です。しかし、REST APIのエンドポイント設計、認証処理、バリデーション、エラーハンドリング、データベースとの接続——これらを一つひとつ手書きしていては、いくら時間があっても足りません。
ここで力を発揮するのがClaude Codeです。Claude Codeはターミナル上で動作するエージェント型AIツールで、プロジェクト全体を理解した上でコードを自律的に生成・編集できます。API連携に必要な定型的なコードの大半を自動生成し、人間は「何を実現したいか」のゴール定義に集中できるようになります。
この記事では、Claude Codeを活用してAPI連携を自動化する具体的な方法を、実践的なコード例とともに解説します。
Claude CodeがAPI連携の自動化に強い3つの理由
Claude CodeでAPI連携を自動化するメリットを整理します。
1. プロジェクト全体を理解して整合性のあるコードを生成する
Claude Codeは単なるコード補完ツールではありません。プロジェクト内の全ファイルを横断的に読み取り、既存の設計パターン・命名規則・ディレクトリ構成を理解した上でコードを生成します。
たとえば、既存のAPIクライアントがaxiosを使っていればそれに合わせ、エラーハンドリングの方針がプロジェクト内で統一されていればその方針を踏襲します。人間が「このプロジェクトではこう書いている」と逐一指示する必要がないのです。
2. ボイラープレートを瞬時に生成する
API連携で最も時間を取られるのが、認証ヘッダーの設定、リクエスト/レスポンスの型定義、バリデーション、リトライ処理といったボイラープレートコードです。Claude Codeはこれらを数秒で生成し、開発者はビジネスロジックに集中できます。
3. ヘッドレスモードでCI/CDに組み込める
Claude Codeにはヘッドレスモード(-pフラグ)があり、対話なしでスクリプトから実行できます。APIのスキーマが変わったときにクライアントコードを自動更新する、テストを自動生成するといった処理をCI/CDパイプラインに組み込めます。
実践1:REST APIクライアントの自動生成
最も基本的なユースケースとして、REST APIクライアントの自動生成を見ていきましょう。
OpenAPI仕様からTypedクライアントを生成する
外部サービスのAPIドキュメント(OpenAPI/Swagger仕様)がある場合、Claude Codeに読み込ませて型安全なクライアントコードを一括生成できます。
# Claude Codeへの指示例
claude "openapi.yamlを読み込んで、TypeScript用のAPIクライアントを生成して。
各エンドポイントに対応するメソッド、リクエスト/レスポンスの型定義、
エラーハンドリング、リトライロジックを含めて。
既存のsrc/lib/api-client.tsのパターンに合わせて。"
Claude Codeが生成するコードの例を示します。
// src/lib/api-client.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
// リクエスト/レスポンスの型定義
interface User {
id: string;
name: string;
email: string;
createdAt: string;
}
interface CreateUserRequest {
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
message?: string;
}
interface ApiErrorResponse {
error: string;
code: string;
details?: Record<string, string[]>;
}
// リトライ設定
const RETRY_CONFIG = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
};
class ApiClient {
private client: AxiosInstance;
constructor(baseURL: string, apiKey: string) {
this.client = axios.create({
baseURL,
timeout: 30000,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// レスポンスインターセプターでエラーを統一処理
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiErrorResponse>) => {
if (error.response) {
const { status, data } = error.response;
throw new ApiError(data.error, data.code, status);
}
throw new ApiError('Network error', 'NETWORK_ERROR', 0);
}
);
}
// リトライ付きリクエスト
private async withRetry<T>(
fn: () => Promise<T>,
retries = RETRY_CONFIG.maxRetries
): Promise<T> {
try {
return await fn();
} catch (error) {
if (retries > 0 && this.isRetryable(error)) {
const delay = Math.min(
RETRY_CONFIG.baseDelay * Math.pow(2, RETRY_CONFIG.maxRetries - retries),
RETRY_CONFIG.maxDelay
);
await new Promise((resolve) => setTimeout(resolve, delay));
return this.withRetry(fn, retries - 1);
}
throw error;
}
}
private isRetryable(error: unknown): boolean {
if (error instanceof ApiError) {
return [408, 429, 500, 502, 503, 504].includes(error.statusCode);
}
return false;
}
// === ユーザー関連エンドポイント ===
async getUsers(): Promise<ApiResponse<User[]>> {
return this.withRetry(() =>
this.client.get('/users').then((res) => res.data)
);
}
async getUserById(id: string): Promise<ApiResponse<User>> {
return this.withRetry(() =>
this.client.get(`/users/${id}`).then((res) => res.data)
);
}
async createUser(data: CreateUserRequest): Promise<ApiResponse<User>> {
return this.client.post('/users', data).then((res) => res.data);
}
async updateUser(id: string, data: Partial<CreateUserRequest>): Promise<ApiResponse<User>> {
return this.client.put(`/users/${id}`, data).then((res) => res.data);
}
async deleteUser(id: string): Promise<void> {
await this.client.delete(`/users/${id}`);
}
}
// カスタムエラークラス
class ApiError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number
) {
super(message);
this.name = 'ApiError';
}
}
export { ApiClient, ApiError };
export type { User, CreateUserRequest, ApiResponse };
ポイントは、Claude Codeに「既存のパターンに合わせて」と指示していることです。プロジェクト内に既にAPIクライアントがあれば、その設計方針を踏襲したコードが生成されます。
自然言語でのエンドポイント追加
既存のAPIクライアントに新しいエンドポイントを追加する場合も、自然言語で指示するだけです。
claude "APIクライアントに決済関連のエンドポイントを追加して。
POST /payments で決済を作成、
GET /payments/:id でステータス確認、
POST /payments/:id/refund で返金処理。
既存のパターンに合わせて型定義も追加して。"
Claude Codeは既存のApiClientクラスの設計パターンを読み取り、一貫性のあるメソッドを追加します。
実践2:データベース連携APIの構築
次に、データベースと連携するAPIサーバーの構築をClaude Codeで自動化する方法を見ていきます。
FastAPI + PostgreSQLの例
Python + FastAPIの構成は、Claude Codeと特に相性が良い組み合わせです。スキーマ定義からCRUD操作まで一気に生成できます。
claude "タスク管理APIを作って。FastAPI + SQLAlchemy + PostgreSQL構成で。
テーブル: tasks (id, title, description, status, priority, created_at, updated_at)
ステータスは todo/in_progress/done の3種類。
CRUD全操作とステータス別フィルタリングを実装して。
Pydanticモデルでバリデーションもかけて。"
Claude Codeが生成するコードの構成例を示します。
# models/task.py - SQLAlchemyモデル
from sqlalchemy import Column, String, Integer, DateTime, Enum
from sqlalchemy.dialects.postgresql import UUID
from datetime import datetime
import uuid
import enum
from database import Base
class TaskStatus(str, enum.Enum):
TODO = "todo"
IN_PROGRESS = "in_progress"
DONE = "done"
class TaskPriority(int, enum.Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
class Task(Base):
__tablename__ = "tasks"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String(200), nullable=False, index=True)
description = Column(String(2000), nullable=True)
status = Column(Enum(TaskStatus), default=TaskStatus.TODO, nullable=False)
priority = Column(Integer, default=TaskPriority.MEDIUM.value, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# schemas/task.py - Pydanticスキーマ(バリデーション)
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
from uuid import UUID
from models.task import TaskStatus
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
status: TaskStatus = TaskStatus.TODO
priority: int = Field(default=2, ge=1, le=3)
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
status: Optional[TaskStatus] = None
priority: Optional[int] = Field(None, ge=1, le=3)
class TaskResponse(BaseModel):
id: UUID
title: str
description: Optional[str]
status: TaskStatus
priority: int
created_at: datetime
updated_at: Optional[datetime]
class Config:
from_attributes = True
# routers/tasks.py - APIエンドポイント
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Optional
from uuid import UUID
from database import get_db
from models.task import Task, TaskStatus
from schemas.task import TaskCreate, TaskUpdate, TaskResponse
router = APIRouter(prefix="/tasks", tags=["tasks"])
@router.get("/", response_model=list[TaskResponse])
async def list_tasks(
status: Optional[TaskStatus] = Query(None, description="ステータスでフィルタ"),
priority: Optional[int] = Query(None, ge=1, le=3, description="優先度でフィルタ"),
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db),
):
query = db.query(Task)
if status:
query = query.filter(Task.status == status)
if priority:
query = query.filter(Task.priority == priority)
return query.offset(skip).limit(limit).all()
@router.post("/", response_model=TaskResponse, status_code=201)
async def create_task(task_data: TaskCreate, db: Session = Depends(get_db)):
task = Task(**task_data.model_dump())
db.add(task)
db.commit()
db.refresh(task)
return task
@router.get("/{task_id}", response_model=TaskResponse)
async def get_task(task_id: UUID, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
return task
@router.patch("/{task_id}", response_model=TaskResponse)
async def update_task(
task_id: UUID, task_data: TaskUpdate, db: Session = Depends(get_db)
):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
update_dict = task_data.model_dump(exclude_unset=True)
for key, value in update_dict.items():
setattr(task, key, value)
db.commit()
db.refresh(task)
return task
@router.delete("/{task_id}", status_code=204)
async def delete_task(task_id: UUID, db: Session = Depends(get_db)):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
このように、1回の指示でモデル・スキーマ・ルーターの3層構成が一気に生成されます。手動で書けば1〜2時間かかる作業が数分で完了します。
実践3:外部APIとの連携パイプライン
実務で特に需要が多いのが、複数の外部サービスのAPIを連携させるパイプラインの構築です。
Slack + データベース連携の例
たとえば「Slackに投稿された特定のメッセージを解析し、データベースに格納する」というケースを考えます。
claude "Slack Webhookで受け取ったメッセージを解析して、
PostgreSQLのfeedbackテーブルに保存するAPIを作って。
Express.js + Prismaで。
メッセージからカテゴリ(bug/feature/question)を
キーワードで自動判別する機能も入れて。
Slack署名検証も実装して。"
// src/routes/slack-webhook.ts
import { Router, Request, Response } from 'express';
import crypto from 'crypto';
import { PrismaClient } from '@prisma/client';
const router = Router();
const prisma = new PrismaClient();
// Slack署名検証ミドルウェア
function verifySlackSignature(req: Request, res: Response, next: Function) {
const signature = req.headers['x-slack-signature'] as string;
const timestamp = req.headers['x-slack-request-timestamp'] as string;
if (!signature || !timestamp) {
return res.status(401).json({ error: 'Missing signature headers' });
}
// タイムスタンプが5分以上古い場合はリプレイ攻撃として拒否
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
return res.status(401).json({ error: 'Request too old' });
}
const sigBaseString = `v0:${timestamp}:${req.body}`;
const mySignature = 'v0=' + crypto
.createHmac('sha256', process.env.SLACK_SIGNING_SECRET!)
.update(sigBaseString)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(mySignature), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
// カテゴリ自動判別
function categorizeMessage(text: string): 'bug' | 'feature' | 'question' {
const lowerText = text.toLowerCase();
const bugKeywords = ['バグ', 'bug', 'エラー', 'error', '不具合', '障害', 'crash'];
const featureKeywords = ['機能', 'feature', '要望', '追加', 'リクエスト', '改善'];
const questionKeywords = ['質問', 'question', '教えて', 'どうすれば', '方法', 'how'];
if (bugKeywords.some((kw) => lowerText.includes(kw))) return 'bug';
if (featureKeywords.some((kw) => lowerText.includes(kw))) return 'feature';
if (questionKeywords.some((kw) => lowerText.includes(kw))) return 'question';
return 'question'; // デフォルト
}
// Webhook受信エンドポイント
router.post('/slack/webhook', verifySlackSignature, async (req: Request, res: Response) => {
const payload = JSON.parse(req.body);
// URL検証チャレンジへの応答
if (payload.type === 'url_verification') {
return res.json({ challenge: payload.challenge });
}
// メッセージイベントの処理
if (payload.event?.type === 'message' && !payload.event?.bot_id) {
const { text, user, channel, ts } = payload.event;
const category = categorizeMessage(text);
await prisma.feedback.create({
data: {
slackUserId: user,
channel: channel,
message: text,
category: category,
slackTimestamp: ts,
},
});
}
res.status(200).json({ ok: true });
});
export default router;
ポイント:セキュリティ対策も自動生成される
上記のコード例で注目すべきは、署名検証やリプレイ攻撃対策が自動的に含まれている点です。Claude Codeは単にCRUD処理を書くだけでなく、セキュリティのベストプラクティスを踏まえたコードを生成します。ただし、生成されたコードは必ず人間がレビューしてください。
実践4:ヘッドレスモードでAPI連携を自動化する
Claude Codeの真価が発揮されるのが、ヘッドレスモードを使った自動化です。対話なしでスクリプトから実行でき、CI/CDパイプラインやcronジョブに組み込めます。
APIスキーマ変更時のクライアント自動更新
外部APIの仕様が変わったとき、クライアントコードを手動で更新するのは手間がかかります。ヘッドレスモードを使えばこの作業を自動化できます。
#!/bin/bash
# scripts/update-api-client.sh
# 最新のOpenAPI仕様をダウンロード
curl -o openapi-latest.yaml https://api.example.com/openapi.yaml
# 差分があれば自動更新
if ! diff -q openapi-latest.yaml openapi.yaml > /dev/null 2>&1; then
echo "API仕様が変更されました。クライアントを更新します。"
cp openapi-latest.yaml openapi.yaml
# Claude Codeをヘッドレスモードで実行
claude -p "openapi.yamlの変更に合わせて、
src/lib/api-client.tsを更新して。
新しいエンドポイントの追加、型定義の変更、
廃止されたエンドポイントの削除を反映して。
既存のテストも更新して。"
echo "更新が完了しました。"
else
echo "API仕様に変更はありません。"
fi
テストの自動生成
API連携コードを書いたら、テストも必要です。これもClaude Codeに任せられます。
claude -p "src/lib/api-client.tsに対するユニットテストを生成して。
Vitestを使用。axiosはモックして。
正常系・異常系・リトライ動作のテストケースを含めて。
__tests__/api-client.test.ts に保存して。"
生成されるテストの一部を示します。
// __tests__/api-client.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios from 'axios';
import { ApiClient, ApiError } from '../src/lib/api-client';
vi.mock('axios');
describe('ApiClient', () => {
let client: ApiClient;
let mockAxiosInstance: any;
beforeEach(() => {
mockAxiosInstance = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
interceptors: {
response: { use: vi.fn() },
},
};
vi.mocked(axios.create).mockReturnValue(mockAxiosInstance);
client = new ApiClient('https://api.example.com', 'test-api-key');
});
describe('getUsers', () => {
it('ユーザー一覧を取得できる', async () => {
const mockUsers = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
];
mockAxiosInstance.get.mockResolvedValue({
data: { data: mockUsers, status: 200 },
});
const result = await client.getUsers();
expect(result.data).toEqual(mockUsers);
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/users');
});
it('サーバーエラー時にリトライする', async () => {
mockAxiosInstance.get
.mockRejectedValueOnce(new ApiError('Server Error', 'SERVER_ERROR', 503))
.mockRejectedValueOnce(new ApiError('Server Error', 'SERVER_ERROR', 503))
.mockResolvedValueOnce({
data: { data: [], status: 200 },
});
const result = await client.getUsers();
expect(result.data).toEqual([]);
expect(mockAxiosInstance.get).toHaveBeenCalledTimes(3);
});
});
describe('createUser', () => {
it('バリデーションエラー時はリトライしない', async () => {
mockAxiosInstance.post.mockRejectedValue(
new ApiError('Validation failed', 'VALIDATION_ERROR', 422)
);
await expect(
client.createUser({ name: '', email: 'invalid' })
).rejects.toThrow(ApiError);
expect(mockAxiosInstance.post).toHaveBeenCalledTimes(1);
});
});
});
実践5:Hooksを活用したAPI連携の品質管理
Claude Code Hooksは、特定のライフサイクルイベント(ファイル保存前、コマンド実行前など)で自動的にスクリプトを実行する仕組みです。API連携の品質管理に活用できます。
APIキーのハードコード防止
API連携コードでよくあるミスが、APIキーのハードコードです。Hooksで自動チェックを設定します。
// .claude/settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "bash -c 'grep -rn \"api_key\\|apiKey\\|API_KEY\\|secret\" \"$CLAUDE_FILE_PATH\" | grep -v \"process\\.env\\|os\\.environ\\|config\\.\" | grep -v \"test\\|mock\\|example\" && echo \"WARNING: APIキーがハードコードされている可能性があります\" && exit 1 || exit 0'"
}
]
}
}
API応答スキーマのバリデーション自動実行
API連携コードを書き換えるたびに、スキーマバリデーションテストを自動で走らせる設定も可能です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "bash -c 'if echo \"$CLAUDE_FILE_PATH\" | grep -q \"api-client\"; then npm run test:api-schema 2>&1 | tail -5; fi'"
}
]
}
}
こうしたHooksの活用により、Claude Codeが生成したAPI連携コードの品質を自動的に担保できます。
API連携自動化で成果を出す4つのコツ
ここまで技術的な実装方法を解説してきましたが、Claude CodeによるAPI連携自動化を成功させるには、ツールの使い方以上にゴール定義の仕方が重要です。
コツ1:仕様を具体的に伝える
悪い例と良い例を比較します。
# NG: 曖昧な指示
claude "APIクライアントを作って"
# OK: 具体的な指示
claude "Stripe APIのPayment Intents用クライアントを作って。
TypeScript + axiosで。
create/retrieve/confirm/cancelの4操作を実装。
金額はcents単位で受け取り、バリデーションで負数を弾く。
リトライは429と5xxのみ、最大3回、指数バックオフで。"
コツ2:既存コードとの整合性を指示する
プロジェクトに既存のコードがある場合、明示的に参照先を伝えます。
claude "src/lib/stripe-client.tsを参考にして、
同じパターンでSendGrid APIクライアントを作って。
エラーハンドリングの方針はstripe-client.tsに合わせて。"
コツ3:段階的に進める
一度にすべてを生成するのではなく、段階的に進めた方が品質が高くなります。
- まずモデルとスキーマを生成
- 生成結果をレビューし、問題があれば修正指示
- CRUD操作のエンドポイントを生成
- テストを生成して動作確認
- 認証・認可の処理を追加
コツ4:CLAUDE.mdにAPIの設計方針を記載する
プロジェクトルートにCLAUDE.mdを配置し、API設計の方針を明文化しておくと、Claude Codeが常にその方針に沿ったコードを生成します。
# CLAUDE.md
## API設計方針
- RESTful設計に準拠する
- レスポンスは { data, status, message } の形式で統一
- エラーレスポンスは { error, code, details } の形式
- ページネーションは cursor-based を採用
- 認証はBearer tokenを使用
- リトライは冪等なメソッド(GET, PUT, DELETE)のみ
- APIバージョニングはURLパス方式(/v1/, /v2/)
## データベース方針
- ORMはPrismaを使用
- IDはUUIDv4
- 論理削除(deletedAtカラム)を採用
- タイムスタンプは UTC で保存
このCLAUDE.mdを用意しておけば、毎回の指示で細かい方針を伝える必要がなくなります。Valuupメソッドでは、このようなAIへのコンテキスト設計を「AIの行動指針書」と位置づけ、組織全体で共有することを推奨しています。
よくある質問
Q. Claude Codeが生成したAPI連携コードはそのまま本番で使えますか?
Claude Codeが生成するコードは品質が高いですが、本番投入前には必ず人間によるレビューが必要です。特にセキュリティ面(認証処理、入力バリデーション、エラーメッセージの情報漏洩)は慎重にチェックしてください。Claude Codeはコードレビューの支援もできるので、生成後に「セキュリティの観点でレビューして」と指示する使い方も有効です。
Q. 既存のAPIプロジェクトにClaude Codeを途中から導入できますか?
可能です。Claude Codeはプロジェクト全体を読み取った上でコードを生成するため、既存のコードベースに合わせた出力をします。まずは小さなタスク(新しいエンドポイントの追加、テストの生成など)から試してみるのが良いでしょう。
Q. APIの認証情報(APIキー)はどう管理すべきですか?
APIキーをコード内にハードコードすることは避け、環境変数で管理してください。Claude Codeに指示する際も「環境変数から読み込む形で」と伝えることが重要です。前述のHooksを使えば、ハードコードの自動検出も可能です。
Q. 対応しているプログラミング言語やフレームワークは?
Claude Codeは主要なバックエンドフレームワークに幅広く対応しています。Node.js系(Express、Fastify、NestJS)、Python系(FastAPI、Django REST Framework、Flask)、Go、Ruby on Rails、Java(Spring Boot)など、一般的なAPI開発環境であれば問題なく利用できます。
まとめ——API連携の自動化は「ゴール定義力」で差がつく
Claude Codeを活用すれば、API連携の開発速度は飛躍的に向上します。
- REST APIクライアントの型安全な自動生成で、ボイラープレートから解放される
- データベース連携のモデル・スキーマ・ルーターを一度の指示で3層同時に構築できる
- ヘッドレスモードでCI/CDパイプラインに組み込み、スキーマ変更への追従を自動化できる
- HooksでAPIキーのハードコード防止やスキーマバリデーションを自動実行できる
しかし、ここで見落としてはならないのが「何を作るか」を正しく定義する力の重要性です。Claude Codeはあくまでツールであり、ゴールの定義が曖昧であれば、いくら高性能でも期待どおりの結果は得られません。
Valuup株式会社では、Claude Codeをはじめとする最新AIツールの活用を通じて、「ゴールを定義してAIを自律駆動させる思考法」を身につけるAI研修を提供しています。API連携の自動化はもちろん、業務プロセス全体をAIで最適化するための設計力・実行力を、実践的なハンズオンを通じて習得できます。
「自社のAPI開発を効率化したい」「AIを活用した開発プロセスの改善に取り組みたい」という方は、ぜひお気軽にご相談ください。
