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:
- API name (e.g., "twitter", "hubspot", "stripe") — used for file name and rate limiter key
- Base URL (e.g.,
https://api.twitter.com/2) - 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>)
- API key in header (e.g.,
- Rate limits from the provider's docs:
- Requests per second (or per 10 seconds)
- Requests per minute (or per hour — convert to per minute)
- 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:
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:
<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:
<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:
# <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:
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:
- Determine the category:
error-fix,api-quirk,schema-pattern,config-gotcha,build-pattern, ordomain-insight - Generate a deterministic ID:
mem-+ first 8 chars of MD5 hash ofcategory:title(use python3) - Read
.outbound-builder-plugin-memory.json(create with{"version":1,"entries":[],"proven_patterns":[]}if missing) - Dedup by ID -- if the same ID exists, increment
success_countand updatedate - Otherwise append a new entry with
success_count: 1,source: "scaffold", andcontext.stage: "scaffold-client" - Rebuild
proven_patternsfrom entries withsuccess_count >= 2 - 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.mdfor client patterns