Skip to content
Skillv1.0.0

vercel-sdk-patterns

Production-ready Vercel REST API patterns with typed fetch wrappers and error handling. Use when integrating with the Vercel API programmatically, building deployment tools, or establishing team codin

by jeremylongshore(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jeremylongshore/tons-of-skills-marketplace (skills/.curated/vercel-sdk-patterns/SKILL.md). Install upstream with npx skills add jeremylongshore/tons-of-skills-marketplace --skill vercel-sdk-patterns. Copyright stays with the author (MIT).

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

Operation Method Endpoint
List projects GET /v9/projects
Get project GET /v9/projects/{idOrName}
Delete project DELETE /v9/projects/{idOrName}
List deployments GET /v6/deployments
Create deployment POST /v13/deployments
Get deployment GET /v13/deployments/{id}
Delete deployment DELETE /v13/deployments/{id}
List env vars GET /v9/projects/{id}/env
Create env var POST /v9/projects/{id}/env
Edit env var PATCH /v9/projects/{id}/env/{envId}
Delete env var DELETE /v9/projects/{id}/env/{envId}
Add domain POST /v9/projects/{id}/domains
Verify domain POST /v9/projects/{id}/domains/{domain}/verify
List teams GET /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

Error Status Solution
forbidden 403 Token lacks scope — regenerate with correct permissions
not_found 404 Check project/deployment ID is correct
rate_limited 429 Use withRetry() wrapper — waits and retries automatically
team_not_found 404 Verify teamId parameter matches your team
bad_request 400 Validate request body matches API schema

Examples

Use a scoped API token for a read-only deployment audit

Create a token limited to the required team and read operations, store it only in the approved CI secret store, and call the deployment listing endpoint with an explicit team ID. Paginate results, redact IDs from public logs when they are not needed, and stop retries on 403 or malformed requests rather than masking a permission error. Revoke the token after the short audit window or rotate it according to the organization’s policy.

Resources

Next Steps

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

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jeremylongshore-tons-of-skills-marketplace-vercel-sdk-patterns/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jeremylongshore-tons-of-skills-marketplace-vercel-sdk-patterns.ocm.jsonjson
{
  "ocm": "1",
  "id": "jeremylongshore-tons-of-skills-marketplace-vercel-sdk-patterns",
  "kind": "skill",
  "name": "vercel-sdk-patterns",
  "description": "Production-ready Vercel REST API patterns with typed fetch wrappers and error handling. Use when integrating with the Vercel API programmatically, building deployment tools, or establishing team coding standards for Vercel API calls. Trigger with phrases like \"vercel SDK patterns\", \"vercel API wrapper\", \"vercel REST API client\", \"vercel best practices\", \"idiomatic vercel API\".",
  "publisher": "jeremylongshore",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "saas",
      "vercel",
      "api",
      "typescript",
      "patterns",
      "skills-sh"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Production-ready Vercel REST API patterns with typed fetch wrappers and error handling. Use when integrating with the Vercel API programmatically, building deployment tools, or establishing team coding standards for Vercel API calls. Trigger with phrases like \"vercel SDK patterns\", \"vercel API wrapper\", \"vercel REST API client\", \"vercel best practices\", \"idiomatic vercel API\"."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/jeremylongshore/tons-of-skills-marketplace",
      "path": "skills/.curated/vercel-sdk-patterns/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/jeremylongshore/tons-of-skills-marketplace/blob/HEAD/skills/.curated/vercel-sdk-patterns/SKILL.md",
      "key": "jeremylongshore/tons-of-skills-marketplace/skills/.curated/vercel-sdk-patterns/SKILL.md"
    },
    "compatibility": "Designed for Claude Code",
    "allowed_tools": [
      "Read,",
      "Write,",
      "Edit"
    ],
    "license": "MIT"
  },
  "instructions": "# Vercel SDK Patterns\n\n## Overview\n\nBuild 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.\n\n## Prerequisites\n\n- Completed `vercel-install-auth` setup\n- TypeScript project with `strict` mode enabled\n- Vercel access token with appropriate scope\n\n## Instructions\n\n### Step 1: Create Typed API Client\n\n```typescript\n// lib/vercel-client.ts\ninterface VercelClientConfig {\n  token: string;\n  teamId?: string;\n  baseUrl?: string",
  "cost": {
    "context_tokens": 2153
  }
}

Fetch it by URL: GET /api/v1/registry/jeremylongshore-tons-of-skills-marketplace-vercel-sdk-patterns/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.