| name | vercel |
| description | Emulated Vercel REST API for local development and testing. Use when the user needs to interact with Vercel API endpoints locally, test Vercel integrations, emulate projects/deployments/domains, set up Vercel OAuth flows, manage environment variables, create API keys, configure protection bypass, emulate Vercel Blob storage, or test without hitting the real Vercel API. Triggers include "Vercel API", "emulate Vercel", "mock Vercel", "test Vercel OAuth", "Vercel integration", "Vercel Blob", "local Vercel", or any task requiring a local Vercel API. |
| allowed-tools | Bash(npx emulate:*), Bash(emulate:*), Bash(curl:*) |
Vercel API Emulator
Fully stateful Vercel REST API emulation with Vercel-style JSON responses and cursor-based pagination.
Start
npx emulate --service vercel
Or programmatically:
import { createEmulator } from 'emulate'
const vercel = await createEmulator({ service: 'vercel', port: 4000 })
Auth
Pass tokens as Authorization: Bearer <token>. All endpoints accept teamId or slug query params for team scoping.
curl http://localhost:4000/v2/user \
-H "Authorization: Bearer test_token_admin"
Team-scoped requests resolve the account from the teamId or slug query parameter. User-scoped requests resolve the account from the authenticated user.
Pointing Your App at the Emulator
Environment Variable
VERCEL_EMULATOR_URL=http://localhost:4000
Vercel SDK / Custom 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 Mapping
| Real Vercel URL | Emulator 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,
}
},
}
Seed Config
tokens:
test_token_admin:
login: admin
scopes: []
vercel:
users:
- username: developer
name: Developer
email: dev@example.com
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
Pagination
Cursor-based pagination using limit, since, and until query params. Responses include a pagination object:
curl "http://localhost:4000/v10/projects?limit=10" \
-H "Authorization: Bearer $TOKEN"
API Endpoints
User & Teams
curl http://localhost:4000/registration
curl http://localhost:4000/v2/user -H "Authorization: Bearer $TOKEN"
curl -X PATCH http://localhost:4000/v2/user \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "New Name", "email": "new@example.com"}'
curl http://localhost:4000/v2/teams -H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v2/teams/my-team -H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:4000/v2/teams \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "new-team", "name": "New Team"}'
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"}'
curl http://localhost:4000/v2/teams/my-team/members -H "Authorization: Bearer $TOKEN"
curl -X POST "http://localhost:4000/v2/teams/team_id/members" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "role": "MEMBER"}'
Roles: OWNER, MEMBER, DEVELOPER, VIEWER.
Projects
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"]}]}'
curl "http://localhost:4000/v10/projects?search=my-app" \
-H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN"
curl -X PATCH http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"framework": "remix"}'
curl -X DELETE http://localhost:4000/v9/projects/my-app \
-H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v1/projects/my-app/promote/aliases \
-H "Authorization: Bearer $TOKEN"
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"}}'
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"]}'
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"]}'
Deployments
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"}}'
curl http://localhost:4000/v13/deployments/dpl_abc123 \
-H "Authorization: Bearer $TOKEN"
curl "http://localhost:4000/v6/deployments?projectId=my-app&target=production&limit=10" \
-H "Authorization: Bearer $TOKEN"
curl -X DELETE http://localhost:4000/v13/deployments/dpl_abc123 \
-H "Authorization: Bearer $TOKEN"
curl -X PATCH http://localhost:4000/v12/deployments/dpl_abc123/cancel \
-H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v2/deployments/dpl_abc123/aliases \
-H "Authorization: Bearer $TOKEN"
curl "http://localhost:4000/v3/deployments/dpl_abc123/events?direction=forward&limit=50" \
-H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v6/deployments/dpl_abc123/files \
-H "Authorization: Bearer $TOKEN"
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
Domains
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}'
curl http://localhost:4000/v9/projects/my-app/domains \
-H "Authorization: Bearer $TOKEN"
curl http://localhost:4000/v9/projects/my-app/domains/example.com \
-H "Authorization: Bearer $TOKEN"
curl -X POST http://localhost:4000/v9/projects/my-app/domains/example.com/verify \
-H "Authorization: Bearer $TOKEN"
Redirect status codes: 301, 302, 307, 308.
Environment Variables
curl "http://localhost:4000/v10/projects/my-app/env?decrypt=true" \
-H "Authorization: Bearer $TOKEN"
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"}'
curl http://localhost:4000/v10/projects/my-app/env/env_abc123 \
-H "Authorization: Bearer $TOKEN"
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"}'
curl -X DELETE http://localhost:4000/v9/projects/my-app/env/env_abc123 \
-H "Authorization: Bearer $TOKEN"
Env var types: system, encrypted, plain, secret, sensitive.
Blob
Implements the Vercel Blob API used by the @vercel/blob SDK (put, head, list, del). Point the SDK at the emulator with two environment variables:
VERCEL_BLOB_API_URL=http://localhost:4000/api/blob
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_mystore_secret
Any token of the form vercel_blob_rw_<storeId>_<secret> is accepted; the store id is parsed from the token.
import { put, head, list, del } from '@vercel/blob'
const blob = await put('avatars/user.png', data, { access: 'public' })
await head(blob.url)
await list({ prefix: 'avatars/' })
await del(blob.url)
Direct HTTP:
BLOB_TOKEN="vercel_blob_rw_mystore_secret"
curl -X PUT "http://localhost:4000/api/blob?pathname=docs/readme.txt" \
-H "Authorization: Bearer $BLOB_TOKEN" \
--data-binary @readme.txt
curl "http://localhost:4000/api/blob?url=docs/readme.txt" \
-H "Authorization: Bearer $BLOB_TOKEN"
curl "http://localhost:4000/api/blob?prefix=docs/" \
-H "Authorization: Bearer $BLOB_TOKEN"
curl -X POST http://localhost:4000/api/blob/delete \
-H "Authorization: Bearer $BLOB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["docs/readme.txt"]}'
curl http://localhost:4000/blob/mystore/docs/readme.txt
Multipart uploads and client (browser) uploads are not supported yet.
API Keys
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"}'
curl "http://localhost:4000/v1/api-keys?teamId=team_abc123" \
-H "Authorization: Bearer $TOKEN"
curl -X DELETE http://localhost:4000/v1/api-keys/ak_abc123 \
-H "Authorization: Bearer $TOKEN"
Created API keys are automatically registered in the token map and can be used as Bearer tokens for all endpoints.
OAuth / Integrations
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"}'
curl http://localhost:4000/login/oauth/userinfo \
-H "Authorization: Bearer $TOKEN"
Common Patterns
Create Project and Deploy
TOKEN="test_token_admin"
BASE="http://localhost:4000"
curl -X POST $BASE/v11/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "framework": "nextjs"}'
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"]}'
curl -X POST $BASE/v13/deployments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-app", "target": "production"}'
OAuth Integration Flow
- Redirect user to
$VERCEL_EMULATOR_URL/oauth/authorize?client_id=...&redirect_uri=...&state=...
- User picks a seeded user on the emulator's UI
- Emulator redirects back with
?code=...&state=...
- Exchange code for token via
POST /login/oauth/token
- Fetch user info via
GET /login/oauth/userinfo
PKCE is supported. Pass code_challenge and code_challenge_method on authorize, then code_verifier on token exchange.
Team-Scoped Requests
All endpoints accept teamId or slug query params:
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"