# Scaffold Client

> Add a new API client with built-in rate limiting, retry, and timeout. Generates from the base client template and adds config entries. Use when integrating a new external API.

- Skill: `ankit4479/scaffold-client` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ankit4479/scaffold-client`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ankit4479/scaffold-client/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: ankit4479 (https://skillmd.com/u/ankit4479)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ankit4479/scaffold-client

---


# Scaffold a New API Client

You create a production-ready API client with rate limiting, retry logic, and timeout handling, plus register it in pipeline config.

## Process

### Step 1: Gather API Details

If `$ARGUMENTS` provides an API name, use it as a starting point. Ask the user:

1. **API name** (e.g., "twitter", "hubspot", "stripe") — used for file name and rate limiter key
2. **Base URL** (e.g., `https://api.twitter.com/2`)
3. **Auth type**:
   - API key in header (e.g., `X-Api-Key: <key>`)
   - Bearer token (e.g., `Authorization: Bearer <token>`)
   - Query parameter (e.g., `?api_key=<key>`)
4. **Rate limits** from the provider's docs:
   - Requests per second (or per 10 seconds)
   - Requests per minute (or per hour — convert to per minute)
5. **Typical response time** (for timeout configuration)

Read `${CLAUDE_SKILL_DIR}/../../imp_doc/api-clients/patterns.md` for client architecture patterns.

### Step 2: Generate Client File

Copy from `${CLAUDE_SKILL_DIR}/../../templates/base-api-client.ts` and customize.

Create `src/clients/<api-name>.ts` with:

- Class name: `<ApiName>Client` (PascalCase)
- Base URL from Step 1
- Auth header/param configured from environment variable
- `rateLimiter.acquire("<api-name>")` before every HTTP call
- Retry logic: 3 attempts, linear backoff (1s, 2s, 3s) for 429/5xx
- Non-transient errors (400, 401, 403, 404) fail immediately
- Timeout via `AbortSignal.timeout()`
- 2-3 example methods matching common API patterns:

```typescript
export class <ApiName>Client {
  private baseUrl: string;
  private apiKey: string;

  constructor() {
    this.baseUrl = "<base-url>";
    this.apiKey = process.env.<API_NAME>_API_KEY!;
  }

  async getResource(id: string): Promise<ResourceResponse> {
    await rateLimiter.acquire("<api-name>");
    // fetch with retry, timeout, auth
  }

  async listResources(params: ListParams): Promise<ListResponse> {
    await rateLimiter.acquire("<api-name>");
    // fetch with retry, timeout, auth
  }
}
```

### Step 3: Add Rate Limit Config

Edit `src/lib/pipeline-config.ts`, add to `RATE_LIMITS`:

```typescript
<apiName>: {
  tokensPerSecond: <N>,        // Provider limit: <actual>/s — using 85% safety margin
  maxBurst: <burst>,           // Allow short bursts above sustained rate
  windowMaxRequests: <N>,      // Provider limit: <actual>/min — using 85%
  windowSeconds: 60,
},
```

**Safety margin rule**: Set token bucket and window limits to ~85% of the provider's documented limits. This accounts for clock drift, concurrent consumers, and measurement differences.

### Step 4: Add Timeout Config

Edit `src/lib/pipeline-config.ts`, add to `FETCH_TIMEOUTS`:

```typescript
<apiName>: <milliseconds>,  // Typical response: <N>ms, timeout at 3x
```

Set timeout to ~3x the typical response time. Minimum 5000ms, maximum 30000ms.

### Step 5: Add Environment Variable

Append to `.env.example`:

```env
# <API Display Name>
<API_NAME>_API_KEY=<YOUR_<API_NAME>_API_KEY>
```

### Step 6: Generate Test File

Create `tests/unit/<api-name>-client.test.ts`:

```typescript
import { describe, it, expect, vi } from "vitest";

describe("<ApiName>Client", () => {
  it("should include auth header in requests", async () => {
    // Verify auth is attached to every request
  });

  it("should retry on 429 and 5xx errors", async () => {
    // Mock fetch to return 429, then 200 — verify retry
  });

  it("should not retry on 4xx client errors", async () => {
    // Mock fetch to return 400 — verify immediate failure
  });

  it("should respect timeout", async () => {
    // Mock fetch to hang — verify abort after timeout
  });
});
```

### Step 7: Report

```
API client "<api-name>" scaffolded:

  src/clients/<api-name>.ts                — Client class with rate limiting + retry
  tests/unit/<api-name>-client.test.ts     — Unit tests

Config updates:
  src/lib/pipeline-config.ts               — Added RATE_LIMITS.<apiName>
  src/lib/pipeline-config.ts               — Added FETCH_TIMEOUTS.<apiName>
  .env.example                             — Added <API_NAME>_API_KEY

Rate limits configured:
  Token bucket: <N>/s (burst <M>) — 85% of provider's <actual>/s
  Window: <N>/min — 85% of provider's <actual>/min
  Timeout: <T>ms

Next steps:
  1. Add your API key to .env
  2. Implement the specific methods you need in the client
  3. Import the client in your task file: import { <ApiName>Client } from "../src/clients/<api-name>"
  4. Run tests: npm test -- tests/unit/<api-name>-client.test.ts
```

### Step 8: Record Learnings

Reflect on the scaffolding process. If you encountered any of the following, record them to `.outbound-builder-plugin-memory.json`:
- Errors or workarounds (e.g., dependency conflicts, config issues)
- Non-obvious behaviors (e.g., a template needed unexpected modifications)
- API quirks or platform-specific gotchas
- Schema decisions that differed from the template defaults

For each learning:
1. Determine the category: `error-fix`, `api-quirk`, `schema-pattern`, `config-gotcha`, `build-pattern`, or `domain-insight`
2. Generate a deterministic ID: `mem-` + first 8 chars of MD5 hash of `category:title` (use python3)
3. Read `.outbound-builder-plugin-memory.json` (create with `{"version":1,"entries":[],"proven_patterns":[]}` if missing)
4. Dedup by ID -- if the same ID exists, increment `success_count` and update `date`
5. Otherwise append a new entry with `success_count: 1`, `source: "scaffold"`, and `context.stage: "scaffold-client"`
6. Rebuild `proven_patterns` from entries with `success_count >= 2`
7. Write the file

Skip this step if nothing noteworthy happened during scaffolding.

## Rules

- Rate limits must use 85% of provider limits (safety margin for clock drift and concurrent consumers)
- Always include BOTH token bucket (per-second smoothing) AND sliding window (per-minute ceiling)
- Auth credentials must come from environment variables — never hardcoded
- Retry only on transient errors (429, 5xx) — fail immediately on 4xx client errors
- Every HTTP call must be preceded by `rateLimiter.acquire("<api-name>")`
- Rate limiting is centralized in `src/lib/pipeline-config.ts` — never configure limits in client files
- Reference `${CLAUDE_SKILL_DIR}/../../imp_doc/api-clients/patterns.md` for client patterns

