agentsclimarketplace

Vercel sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/vercel-pack/skills/vercel-sdk-patterns

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill vercel-sdk-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Production-ready Vercel REST API patterns with typed fetch wrappers and error handling.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.6 KB, as published. Nobody here has run it

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 vercel-install-auth setup
  • TypeScript project with strict mode enabled
  • 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

OperationMethodEndpoint
List projectsGET/v9/projects
Get projectGET/v9/projects/{idOrName}
Delete projectDELETE/v9/projects/{idOrName}
List deploymentsGET/v6/deployments
Create deploymentPOST/v13/deployments
Get deploymentGET/v13/deployments/{id}
Delete deploymentDELETE/v13/deployments/{id}
List env varsGET/v9/projects/{id}/env
Create env varPOST/v9/projects/{id}/env
Edit env varPATCH/v9/projects/{id}/env/{envId}
Delete env varDELETE/v9/projects/{id}/env/{envId}
Add domainPOST/v9/projects/{id}/domains
Verify domainPOST/v9/projects/{id}/domains/{domain}/verify
List teamsGET/v2/teams

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

ErrorStatusSolution
forbidden403Token lacks scope — regenerate with correct permissions
not_found404Check project/deployment ID is correct
rate_limited429Use withRetry() wrapper — waits and retries automatically
team_not_found404Verify teamId parameter matches your team
bad_request400Validate request body matches API schema

Resources

Next Steps

Proceed to vercel-deploy-preview for preview deployment workflows.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.