vercel について
vercel はアプリケーションのテスト向けの、状態を保持する Vercel REST API エミュレーターです。Vercel 形式の JSON 応答とカーソルベースのページネーションを備え、プロジェクト、デプロイ、ドメイン、Blob、OAuth などを扱います。
主な機能
- 01Vercel REST API の模擬
- 02カーソルベースのページネーション
- 03プロジェクト、デプロイ、ドメイン管理に対応
- 04環境変数と Blob API の模擬
- 05チームスコープと OAuth に対応
活用シーン
- 01Vercel API クライアントのテスト
- 02プロジェクト作成とデプロイ手順の検証
- 03Blob のファイル操作のテスト
- 04OAuth とチームスコープ付きリクエストの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ emulate --skill vercelインストール後、Claude Code で/vercelを入力して読み込まれたか確認します。
詳細
Vercel API エミュレーター
Vercel 形式の JSON レスポンスとカーソルベースのページネーションに対応した、完全な状態管理機能を備える Vercel REST API エミュレーションです。
起動
ローカルリスナーは、デフォルトで 127.0.0.1 にバインドされます。コンテナーやほかのマシンからアクセスするには、CLI に --host 0.0.0.0 を渡すか、createEmulator に hostname: '0.0.0.0' を渡します。これらのクライアントからアクセスできる URL を通知するには、--base-url または baseUrl を設定してください。
# Vercel only
npx emulate --service vercel
# Default port
# http://localhost:4000
または、プログラムから起動します。
import { createEmulator } from 'emulate'
const vercel = await createEmulator({ service: 'vercel', port: 4000 })
// vercel.url === 'http://localhost:4000'
認証
トークンは Authorization: Bearer <token> として渡します。すべてのエンドポイントで、チームのスコープを指定するための teamId または slug クエリパラメーターを使用できます。
curl http://localhost:4000/v2/user \
-H "Authorization: Bearer test_token_admin"
チームスコープのリクエストでは、teamId または slug クエリパラメーターからアカウントを解決します。ユーザースコープのリクエストでは、認証済みユーザーからアカウントを解決します。
アプリの接続先をエミュレーターに変更する
環境変数
VERCEL_EMULATOR_URL=http://localhost:4000
Vercel SDK /カスタム Fetch
const VERCEL_API = process.env.VERCEL_EMULATOR_URL ?? 'https://api.vercel.com'
const res = await fetch(`${VERCEL_API}/v10/projects`, {
headers: { Authorization: `Bearer ${token}` },
})
OAuth URL のマッピング
| 実際の Vercel URL | エミュレーター URL |
|---|---|
https://vercel.com/integrations/oauth/authorize | $VERCEL_EMULATOR_URL/oauth/authorize |
https://api.vercel.com/login/oauth/token | $VERCEL_EMULATOR_URL/login/oauth/token |
https://api.vercel.com/login/oauth/userinfo | $VERCEL_EMULATOR_URL/login/oauth/userinfo |
Auth.js / NextAuth.js
{
id: 'vercel',
name: 'Vercel',
type: 'oauth',
authorization: {
url: `${process.env.VERCEL_EMULATOR_URL}/oauth/authorize`,
},
token: {
url: `${process.env.VERCEL_EMULATOR_URL}/login/oauth/token`,
},
userinfo: {
url: `${process.env.VERCEL_EMULATOR_URL}/login/oauth/userinfo`,
},
clientId: process.env.VERCEL_CLIENT_ID,
clientSecret: process.env.VERCEL_CLIENT_SECRET,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
}
シード設定
tokens:
test_token_admin:
login: admin
scopes: []
vercel:
users:
- username: developer
name: Developer
email: [email protected]
teams:
- slug: my-team
name: My Team
description: Engineering team
projects:
- name: my-app
team: my-team
framework: nextjs
buildCommand: next build
outputDirectory: .next
rootDirectory: null
nodeVersion: "20.x"
envVars:
- key: DATABASE_URL
value: postgres://localhost/mydb
type: encrypted
target: [production, preview]
integrations:
- client_id: oac_abc123
client_secret: secret_abc123
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercel
ページネーション
limit、since、until クエリパラメーターを使用するカーソルベースのページネーションです。レスポンスには pagination オブジェクトが含まれます。
curl "http://localhost:4000/v10/projects?limit=10" \
-H "Authorization: Bearer $TOKEN"
API エンドポイント
ユーザーとチーム
# Registration check
curl http://localhost:4000/registration
# Authenticated user
curl http://localhost:4000/v2/user -H "Authorization: Bearer $TOKEN"
# Update user
curl -X PATCH http://localhost:4000/v2/user \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "New Name", "email": "[email protected]"}'
# List teams (cursor paginated)
curl http://localhost:4000/v2/teams -H "Authorization: Bearer $TOKEN"
# Get team (by ID or slug)
curl http://localhost:4000/v2/teams/my-team -H "Authorization: Bearer $TOKEN"
# Create team
curl -X POST http://localhost:4000/v2/teams \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "new-team", "name": "New Team"}'
# Update team (name, slug, description)
curl -X PATCH http://localhost:4000/v2/teams/my-team \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Team", "description": "New description"}'
# List members
curl http://localhost:4000/v2/teams/my-team/members -H "Authorization: Bearer $TOKEN"
# Add member (by uid or email, with role)
curl -X POST "http://localhost:4000/v2/teams/team_id/members" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "role": "MEMBER"}'
ロール:OWNER、MEMBER、DEVELOPER、VIEWER。
プロジェクト
# Create project (with optional env vars, git, and build config)
curl -X POST http://localhost:4000/v11/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "framework": "nextjs", "buildCommand": "next build", "outputDirectory": ".next", "nodeVersion": "20.x", "environmentVariables": [{"key": "API_KEY", "value": "secret", "type": "encrypted", "target": ["production"]}]}'
# List projects (search, cursor pagination)
curl "http://localhost:4000/v10/projects?search=my-app" \
-H "Authorization: Bearer $TOKEN"
# Get project (includes env vars)
curl http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN"
# Update project (framework, buildCommand, devCommand, installCommand,
# outputDirectory, rootDirectory, nodeVersion, serverlessFunctionRegion,
# publicSource, autoAssignCustomDomains, gitForkProtection,
# commandForIgnoringBuildStep)
curl -X PATCH http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"framework": "remix"}'
# Delete project (cascades deployments, domains, env vars, protection bypasses)
curl -X DELETE http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN"
# Promote aliases status
curl http://localhost:4000/v1/projects/my-app/promote/aliases \
-H "Authorization: Bearer $TOKEN"
# Protection bypass: generate, revoke, regenerate
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"generate": {"note": "CI preview", "scope": "deployment"}}'
# Revoke protection bypass secrets
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"revoke": ["secret_to_revoke"]}'
# Regenerate protection bypass secrets
curl -X PATCH http://localhost:4000/v1/projects/my-app/protection-bypass \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"regenerate": ["old_secret"]}'
デプロイ
# Create deployment (auto-transitions to READY)
curl -X POST http://localhost:4000/v13/deployments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "target": "production", "meta": {"commit": "abc123"}, "regions": ["iad1"], "gitSource": {"type": "github", "ref": "main", "sha": "abc123", "repoId": "123", "org": "my-org", "repo": "my-app", "message": "Deploy", "authorName": "dev", "commitAuthorName": "dev"}}'
# Targets: "production", "preview", "staging"
# Get deployment (by ID or URL)
curl http://localhost:4000/v13/deployments/dpl_abc123 \
-H "Authorization: Bearer $TOKEN"
# List deployments (filter by projectId, app, target, state; cursor paginated)
curl "http://localhost:4000/v6/deployments?projectId=my-app&target=production&limit=10" \
-H "Authorization: Bearer $TOKEN"
# List deployments (filter by commit SHA)
curl "http://localhost:4000/v7/deployments?sha=abc123" \
-H "Authorization: Bearer $TOKEN"
# Delete deployment
curl -X DELETE http://localhost:4000/v13/deployments/dpl_abc123 \
-H "Authorization: Bearer $TOKEN"
# Cancel building deployment
curl -X PATCH http://localhost:4000/v12/deployments/dpl_abc123/cancel \
-H "Authorization: Bearer $TOKEN"
# List deployment aliases
curl http://localhost:4000/v2/deployments/dpl_abc123/aliases \
-H "Authorization: Bearer $TOKEN"
# Get build events/logs (supports direction, limit)
curl "http://localhost:4000/v3/deployments/dpl_abc123/events?direction=forward&limit=50" \
-H "Authorization: Bearer $TOKEN"
# List deployment files
curl http://localhost:4000/v6/deployments/dpl_abc123/files \
-H "Authorization: Bearer $TOKEN"
# Upload file (by SHA digest)
curl -X POST http://localhost:4000/v2/files \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
-H "x-vercel-digest: sha256hash" \
--data-binary @file.txt
ドメイン
# Add domain (with optional redirect, gitBranch, customEnvironmentId)
curl -X POST http://localhost:4000/v10/projects/my-app/domains \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "example.com", "redirect": null, "redirectStatusCode": null, "gitBranch": null}'
# *.vercel.app domains are auto-verified
# List domains (cursor paginated)
curl http://localhost:4000/v9/projects/my-app/domains \
-H "Authorization: Bearer $TOKEN"
# Get, update, remove domain
curl http://localhost:4000/v9/projects/my-app/domains/example.com \
-H "Authorization: Bearer $TOKEN"
# Verify domain
curl -X POST http://localhost:4000/v9/projects/my-app/domains/example.com/verify \
-H "Authorization: Bearer $TOKEN"
リダイレクトのステータスコード:301、302、307、308。
環境変数
# List env vars (with decrypt option; filter by gitBranch, customEnvironmentId)
curl "http://localhost:4000/v10/projects/my-app/env?decrypt=true" \
-H "Authorization: Bearer $TOKEN"
# Create env vars (single, batch, or upsert)
curl -X POST "http://localhost:4000/v10/projects/my-app/env?upsert=true" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "API_KEY", "value": "secret123", "type": "encrypted", "target": ["production", "preview"], "comment": "API key for service"}'
# Get env var
curl http://localhost:4000/v10/projects/my-app/env/env_abc123 \
-H "Authorization: Bearer $TOKEN"
# Update env var
curl -X PATCH http://localhost:4000/v9/projects/my-app/env/env_abc123 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "newsecret"}'
# Delete env var
curl -X DELETE http://localhost:4000/v9/projects/my-app/env/env_abc123 \
-H "Authorization: Bearer $TOKEN"
環境変数のタイプ:system、encrypted、plain、secret、sensitive。
Blob
@vercel/blob SDK が使用する Vercel Blob API(put、head、list、del)を実装しています。2 つの環境変数を使って、SDK の接続先をエミュレーターに設定します。
VERCEL_BLOB_API_URL=http://localhost:4000/api/blob
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_mystore_secret
vercel_blob_rw_<storeId>_<secret> 形式のトークンはすべて受け付けます。トークンからストア ID を解析します。
import { put, head, list, del } from '@vercel/blob'
const blob = await put('avatars/user.png', data, { access: 'public' })
// blob.url serves the bytes from the emulator
await head(blob.url)
await list({ prefix: 'avatars/' })
await del(blob.url)
HTTP による直接操作:
BLOB_TOKEN="vercel_blob_rw_mystore_secret"
# Upload (honors x-add-random-suffix, x-allow-overwrite, x-content-type,
# x-cache-control-max-age, x-if-match headers)
curl -X PUT "http://localhost:4000/api/blob?pathname=docs/readme.txt" \
-H "Authorization: Bearer $BLOB_TOKEN" \
--data-binary @readme.txt
# Copy (x-if-match is compared with the source blob's ETag;
# an existing destination is always replaced)
curl -X PUT "http://localhost:4000/api/blob?pathname=docs/copy.txt&fromUrl=docs/readme.txt" \
-H "Authorization: Bearer $BLOB_TOKEN"
# Metadata (head)
curl "http://localhost:4000/api/blob?url=docs/readme.txt" \
-H "Authorization: Bearer $BLOB_TOKEN"
# List (prefix, limit, cursor, mode=folded; folded pages share one
# sorted cursor across folders and root blobs)
curl "http://localhost:4000/api/blob?prefix=docs/" \
-H "Authorization: Bearer $BLOB_TOKEN"
# Delete
curl -X POST http://localhost:4000/api/blob/delete \
-H "Authorization: Bearer $BLOB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["docs/readme.txt"]}'
# Start a multipart upload (returns { key, uploadId })
curl -X POST "http://localhost:4000/api/blob/mpu?pathname=docs/large.bin" \
-H "Authorization: Bearer $BLOB_TOKEN" \
-H "x-mpu-action: create"
# Serve content (public, no auth; ?download=1 forces attachment)
curl http://localhost:4000/blob/mystore/docs/readme.txt
アップロードと削除のルール:
- パス名の先頭にあるスラッシュは 1 つ削除されます。そのため、
/docs/readme.txtはdocs/readme.txtとして保存されます。実際のサービスと同様に、HTTP ではそれ以外の//は受け付けるため、//a.txtは/a.txtとして保存されます(@vercel/blobSDK はリクエスト送信前に//を拒否します)。このルールはコピー先とマルチパートアップロードにも適用されます。 - コンテンツタイプは、
x-content-type、既知のタイプに一致するパス名の拡張子、リクエストのContent-Typeヘッダー、application/octet-streamの順に取得されます。 x-cache-control-max-ageは最小値が 60 秒です(0はpublic, max-age=60として保存されます)。値が指定されていない場合、または無効な場合は、デフォルトで 30 日になります。urls配列がない場合、または空の場合、削除リクエストは 400Missing urls in request bodyを返します。不正な JSON の場合は 400Cannot delete more than 1,000 urls at once or body is malformed、1,000 件を超える URL が含まれる場合は 400Cannot delete more than 1,000 urls at once、文字列以外の値が含まれる場合は 400Some urls are malformedを返します。これらの場合、削除は行われません。
マルチパートアップロードは作成できますが、パーツのアップロードとアップロードの完了にはまだ対応しておらず、400 が返されます。クライアント(ブラウザー)からのアップロードにはまだ対応していません。
API キー
# Create API key (optional teamId scope)
curl -X POST "http://localhost:4000/v1/api-keys?teamId=team_abc123" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "CI Deploy Key"}'
# List API keys (optional teamId filter)
curl "http://localhost:4000/v1/api-keys?teamId=team_abc123" \
-H "Authorization: Bearer $TOKEN"
# Delete API key
curl -X DELETE http://localhost:4000/v1/api-keys/ak_abc123 \
-H "Authorization: Bearer $TOKEN"
作成された API キーは自動的にトークンマップに登録され、すべてのエンドポイントで Bearer トークンとして使用できます。
OAuth /インテグレーション
# Authorize (browser flow, shows user picker)
# GET /oauth/authorize?client_id=...&redirect_uri=...&scope=...&state=...
# Token exchange (supports PKCE; accepts JSON or form-urlencoded)
curl -X POST http://localhost:4000/login/oauth/token \
-H "Content-Type: application/json" \
-d '{"client_id": "oac_abc123", "client_secret": "secret_abc123", "code": "<code>", "redirect_uri": "http://localhost:3000/api/auth/callback/vercel"}'
# User info (returns sub, email, email_verified, name, preferred_username, picture)
curl http://localhost:4000/login/oauth/userinfo \
-H "Authorization: Bearer $TOKEN"
よくあるパターン
プロジェクトを作成してデプロイする
TOKEN="test_token_admin"
BASE="http://localhost:4000"
# Create project
curl -X POST $BASE/v11/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "framework": "nextjs"}'
# Add env var
curl -X POST $BASE/v10/projects/my-app/env \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "DATABASE_URL", "value": "postgres://...", "type": "encrypted", "target": ["production"]}'
# Create deployment
curl -X POST $BASE/v13/deployments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "target": "production"}'
OAuth 統合フロー
- ユーザーを
$VERCEL_EMULATOR_URL/oauth/authorize?client_id=...&redirect_uri=...&state=...にリダイレクトします。 - ユーザーがエミュレーターの UI でシード済みユーザーを選択します。
- エミュレーターが
?code=...&state=...を付けてリダイレクトします。 POST /login/oauth/tokenでコードをトークンに交換します。GET /login/oauth/userinfoでユーザー情報を取得します。
PKCE に対応しています。認可リクエストに code_challenge と code_challenge_method を渡し、トークン交換時に code_verifier を渡します。
チームスコープのリクエスト
すべてのエンドポイントで、クエリパラメーター teamId または slug を指定できます。
curl "http://localhost:4000/v10/projects?teamId=team_abc123" \
-H "Authorization: Bearer $TOKEN"
curl "http://localhost:4000/v10/projects?slug=my-team" \
-H "Authorization: Bearer $TOKEN"
組み込みエミュレーターと併せてカスタムエミュレーターを使用する
npx emulate init --custom inventory を使用して、サードパーティ API エミュレーターをひな形から作成し、テストします。emulate の defineConfig を使い、{ emulator: "vercel" } などの組み込みエントリと併せて、emulate.config.ts に登録します。npx emulate start --watch を実行すると、インポートが再読み込みされ、表示された /_emulate URL でカスタムの状態を確認できます。再読み込みが成功すると、実行状態はシードにリセットされます。既存のフラットなシード設定も引き続き使用できます。明示的なファイルを選択するには --config を指定します。サードパーティ API エミュレーターの作成とテストについては、https://emulate.dev/docs/custom-emulators を参照してください。