Claude-code-plugins vercel-sdk-patterns
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/vercel-pack/skills/vercel-sdk-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-vercel-sdk-patterns && rm -rf "$T"
manifest:
plugins/saas-packs/vercel-pack/skills/vercel-sdk-patterns/SKILL.mdsource content
Vercel SDK Patterns
Overview
Build a typed, production-ready wrapper around the Vercel REST API (
api.vercel.com). Covers authentication, pagination, error handling, retry logic, and common endpoint patterns for deployments, projects, and environment variables.
Prerequisites
- Completed
setupvercel-install-auth - TypeScript project with
mode enabledstrict - Vercel access token with appropriate scope
Instructions
Step 1: Create Typed API Client
// lib/vercel-client.ts interface VercelClientConfig { token: string; teamId?: string; baseUrl?: string; } interface VercelError { error: { code: string; message: string }; } class VercelClient { private token: string; private teamId?: string; private baseUrl: string; constructor(config: VercelClientConfig) { this.token = config.token; this.teamId = config.teamId; this.baseUrl = config.baseUrl ?? 'https://api.vercel.com'; } private async request<T>( method: string, path: string, body?: unknown ): Promise<T> { const url = new URL(path, this.baseUrl); if (this.teamId) url.searchParams.set('teamId', this.teamId); const res = await fetch(url.toString(), { method, headers: { Authorization: `Bearer ${this.token}`, 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const err: VercelError = await res.json(); throw new VercelApiError(res.status, err.error.code, err.error.message); } // 204 No Content if (res.status === 204) return undefined as T; return res.json() as Promise<T>; } // --- Projects --- async listProjects(limit = 20) { return this.request<{ projects: VercelProject[] }>( 'GET', `/v9/projects?limit=${limit}` ); } async getProject(idOrName: string) { return this.request<VercelProject>('GET', `/v9/projects/${idOrName}`); } // --- Deployments --- async listDeployments(projectId?: string, limit = 20) { const params = new URLSearchParams({ limit: String(limit) }); if (projectId) params.set('projectId', projectId); return this.request<{ deployments: VercelDeployment[] }>( 'GET', `/v6/deployments?${params}` ); } async getDeployment(idOrUrl: string) { return this.request<VercelDeployment>( 'GET', `/v13/deployments/${idOrUrl}` ); } // --- Environment Variables --- async listEnvVars(projectId: string) { return this.request<{ envs: VercelEnvVar[] }>( 'GET', `/v9/projects/${projectId}/env` ); } async createEnvVar(projectId: string, envVar: CreateEnvVarInput) { return this.request<VercelEnvVar>( 'POST', `/v9/projects/${projectId}/env`, envVar ); } // --- Domains --- async listDomains(projectId: string) { return this.request<{ domains: VercelDomain[] }>( 'GET', `/v9/projects/${projectId}/domains` ); } async addDomain(projectId: string, domain: string) { return this.request<VercelDomain>( 'POST', `/v9/projects/${projectId}/domains`, { name: domain } ); } }
Step 2: Define Types
// lib/vercel-types.ts interface VercelProject { id: string; name: string; framework: string | null; latestDeployments: VercelDeployment[]; targets: Record<string, VercelDeployment>; createdAt: number; updatedAt: number; } interface VercelDeployment { uid: string; name: string; url: string; state: 'BUILDING' | 'ERROR' | 'INITIALIZING' | 'QUEUED' | 'READY' | 'CANCELED'; target: 'production' | 'preview' | null; createdAt: number; buildingAt: number; ready: number; meta: Record<string, string>; } interface VercelEnvVar { id: string; key: string; value: string; type: 'system' | 'encrypted' | 'plain' | 'sensitive'; target: ('production' | 'preview' | 'development')[]; createdAt: number; updatedAt: number; } interface CreateEnvVarInput { key: string; value: string; type: 'encrypted' | 'plain' | 'sensitive'; target: ('production' | 'preview' | 'development')[]; } interface VercelDomain { name: string; verified: boolean; redirect: string | null; gitBranch: string | null; createdAt: number; updatedAt: number; }
Step 3: Custom Error Class
// lib/vercel-errors.ts class VercelApiError extends Error { constructor( public status: number, public code: string, message: string ) { super(`Vercel API ${status}: [${code}] ${message}`); this.name = 'VercelApiError'; } get isRateLimit(): boolean { return this.status === 429; } get isNotFound(): boolean { return this.status === 404; } get isUnauthorized(): boolean { return this.status === 401 || this.status === 403; } }
Step 4: Retry with Exponential Backoff
// lib/vercel-retry.ts async function withRetry<T>( fn: () => Promise<T>, maxRetries = 3, baseDelayMs = 1000 ): Promise<T> { for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (err) { if (err instanceof VercelApiError && err.isRateLimit && attempt < maxRetries) { const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 500; console.warn(`Rate limited. Retrying in ${Math.round(delay)}ms...`); await new Promise(r => setTimeout(r, delay)); continue; } throw err; } } throw new Error('Unreachable'); } // Usage: // const projects = await withRetry(() => client.listProjects());
Step 5: Paginated Fetching
// lib/vercel-pagination.ts async function* paginateDeployments( client: VercelClient, projectId: string, pageSize = 100 ): AsyncGenerator<VercelDeployment[]> { let until: number | undefined; while (true) { const params = new URLSearchParams({ limit: String(pageSize) }); if (until) params.set('until', String(until)); if (projectId) params.set('projectId', projectId); const { deployments } = await client.listDeployments(projectId, pageSize); if (deployments.length === 0) break; yield deployments; until = deployments[deployments.length - 1].createdAt; if (deployments.length < pageSize) break; } }
API Endpoint Quick Reference
| Operation | Method | Endpoint |
|---|---|---|
| List projects | GET | |
| Get project | GET | |
| Delete project | DELETE | |
| List deployments | GET | |
| Create deployment | POST | |
| Get deployment | GET | |
| Delete deployment | DELETE | |
| List env vars | GET | |
| Create env var | POST | |
| Edit env var | PATCH | |
| Delete env var | DELETE | |
| Add domain | POST | |
| Verify domain | POST | |
| List teams | GET | |
Output
- Type-safe Vercel API client with full TypeScript coverage
- Custom error class with semantic helpers (isRateLimit, isNotFound)
- Automatic retry with exponential backoff for 429 responses
- Paginated data fetching for large result sets
Error Handling
| Error | Status | Solution |
|---|---|---|
| 403 | Token lacks scope — regenerate with correct permissions |
| 404 | Check project/deployment ID is correct |
| 429 | Use wrapper — waits and retries automatically |
| 404 | Verify parameter matches your team |
| 400 | Validate request body matches API schema |
Resources
Next Steps
Proceed to
vercel-deploy-preview for preview deployment workflows.