Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

Patterns for programmatic deployment via Vercel API. Use this skill when implementing deployment workflows, managing projects, configuring environment variables, or setting up domains.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name vercel-api
description Patterns for programmatic deployment via Vercel API. Use this skill when implementing deployment workflows, managing projects, configuring environment variables, or setting up domains.

Vercel API Patterns

API Client Setup

const VERCEL_API = 'https://api.vercel.com'

interface VercelClientConfig {
  token: string
  teamId?: string
}

class VercelClient {
  constructor(private config: VercelClientConfig) {}

  private async request<T>(
    path: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = new URL(path, VERCEL_API)

    if (this.config.teamId) {
      url.searchParams.set('teamId', this.config.teamId)
    }

    const response = await fetch(url, {
      ...options,
      headers: {
        Authorization: `Bearer ${this.config.token}`,
        'Content-Type': 'application/json',
        ...options.headers,
      },
    })

    if (!response.ok) {
      const error = await response.json()
      throw new VercelAPIError(error.error.message, response.status)
    }

    return response.json()
  }
}

Project Creation

interface CreateProjectRequest {
  name: string
  framework?: 'nextjs' | 'vite' | 'remix' | null
  gitRepository?: {
    type: 'github' | 'gitlab' | 'bitbucket'
    repo: string
  }
  buildCommand?: string
  outputDirectory?: string
  rootDirectory?: string
}

async createProject(data: CreateProjectRequest) {
  return this.request<Project>('/v10/projects', {
    method: 'POST',
    body: JSON.stringify(data),
  })
}

Environment Variables

Set Environment Variables

interface EnvVariable {
  key: string
  value: string
  target: ('production' | 'preview' | 'development')[]
  type: 'plain' | 'secret' | 'encrypted'
}

async setEnvVariables(projectId: string, variables: EnvVariable[]) {
  return this.request(`/v10/projects/${projectId}/env`, {
    method: 'POST',
    body: JSON.stringify(variables),
  })
}

Get Environment Variables

async getEnvVariables(projectId: string) {
  return this.request<{ envs: EnvVariable[] }>(
    `/v9/projects/${projectId}/env`
  )
}

Deployment

Create Deployment from Files

interface DeploymentFile {
  file: string
  data: string // base64 encoded
}

async createDeployment(projectId: string, files: DeploymentFile[]) {
  return this.request<Deployment>('/v13/deployments', {
    method: 'POST',
    body: JSON.stringify({
      name: projectId,
      files,
      projectSettings: {
        framework: 'nextjs',
      },
    }),
  })
}

Check Deployment Status

type DeploymentState =
  | 'QUEUED'
  | 'BUILDING'
  | 'READY'
  | 'ERROR'
  | 'CANCELED'

async getDeployment(deploymentId: string) {
  return this.request<Deployment>(`/v13/deployments/${deploymentId}`)
}

async waitForDeployment(
  deploymentId: string,
  timeout = 300000
): Promise<Deployment> {
  const start = Date.now()

  while (Date.now() - start < timeout) {
    const deployment = await this.getDeployment(deploymentId)

    if (deployment.readyState === 'READY') {
      return deployment
    }

    if (deployment.readyState === 'ERROR') {
      throw new Error(`Deployment failed: ${deployment.errorMessage}`)
    }

    await new Promise((r) => setTimeout(r, 2000))
  }

  throw new Error('Deployment timeout')
}

Domain Configuration

Add Domain

async addDomain(projectId: string, domain: string) {
  return this.request(`/v10/projects/${projectId}/domains`, {
    method: 'POST',
    body: JSON.stringify({ name: domain }),
  })
}

Configure Subdomain

Battery uses subdomains for deployed apps: {app-name}.{org}.battery.app

async configureBatteryDomain(
  projectId: string,
  appName: string,
  orgSlug: string
) {
  const domain = `${appName}.${orgSlug}.battery.app`
  return this.addDomain(projectId, domain)
}

Error Handling

class VercelAPIError extends Error {
  constructor(
    message: string,
    public statusCode: number
  ) {
    super(message)
    this.name = 'VercelAPIError'
  }
}

// Handle common errors
try {
  await client.createProject({ name: 'my-app' })
} catch (error) {
  if (error instanceof VercelAPIError) {
    switch (error.statusCode) {
      case 400:
        // Invalid request
        break
      case 401:
        // Invalid token
        break
      case 403:
        // Insufficient permissions
        break
      case 409:
        // Project already exists
        break
    }
  }
}

Rate Limiting

Vercel API has rate limits. Implement backoff:

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      if (
        error instanceof VercelAPIError &&
        error.statusCode === 429 &&
        i < maxRetries - 1
      ) {
        await new Promise((r) => setTimeout(r, 2 ** i * 1000))
        continue
      }
      throw error
    }
  }
  throw new Error('Max retries exceeded')
}

Key Endpoints Reference

Operation Method Endpoint
List projects GET /v9/projects
Create project POST /v10/projects
Get project GET /v9/projects/{idOrName}
Delete project DELETE /v9/projects/{idOrName}
Create deployment POST /v13/deployments
Get deployment GET /v13/deployments/{id}
List deployments GET /v6/deployments
Set env vars POST /v10/projects/{id}/env
Add domain POST /v10/projects/{id}/domains