Claude
Skills
Sign in
Back

designing-sdks

Included with Lifetime
$97 forever

Design production-ready SDKs with retry logic, error handling, pagination, and multi-language support. Use when building client libraries for APIs or creating developer-facing SDK interfaces.

Design

What this skill does


# SDK Design

Design client libraries (SDKs) with excellent developer experience through intuitive APIs, robust error handling, automatic retries, and consistent patterns across programming languages.

## When to Use This Skill

Use when building a client library for a REST API, creating internal service SDKs, implementing retry logic with exponential backoff, handling authentication patterns, creating typed error hierarchies, implementing pagination with async iterators, or designing streaming APIs for real-time data.

## Core Architecture Patterns

### Client → Resources → Methods

Organize SDK code hierarchically:

```
Client (config: API key, base URL, retries, timeout)
├─ Resources (users, payments, posts)
│   ├─ create(), retrieve(), update(), delete()
│   └─ list() (with pagination)
└─ Top-Level Methods (convenience)
```

**Resource-Based (Stripe style):**

```typescript
const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: '[email protected]' })
```

Use for APIs <100 methods. Prioritizes developer experience.

**Command-Based (AWS SDK v3):**

```typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
await client.send(new PutObjectCommand({ Bucket: '...' }))
```

Use for APIs >100 methods. Prioritizes bundle size and tree-shaking.

For detailed architectural guidance, see `references/architecture-patterns.md`.

## Language-Specific Patterns

### TypeScript: Async-Only

```typescript
const user = await client.users.create({ email: '[email protected]' })
```

All methods return Promises. Avoid callbacks.

### Python: Dual Sync/Async

```python
# Sync
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='[email protected]')

# Async
async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='[email protected]')
```

Provide both clients. Users choose based on architecture.

### Go: Sync with Context

```go
client := apiclient.New("api_key")
user, err := client.Users().Create(ctx, req)
```

Use context.Context for timeout and cancellation.

## Authentication

### API Key (Most Common)

```typescript
const client = new APIClient({ apiKey: process.env.API_KEY })
```

Store keys in environment variables, never hardcode.

### OAuth Token Refresh

```typescript
const client = new APIClient({
  clientId: 'id',
  clientSecret: 'secret',
  refreshToken: 'token',
  onTokenRefresh: (newToken) => saveToken(newToken)
})
```

SDK automatically refreshes tokens before expiry.

### Bearer Token Per-Request

```typescript
await client.users.list({
  headers: { Authorization: `Bearer ${userToken}` }
})
```

Use for multi-tenant applications.

See `references/authentication.md` for OAuth flows, JWT handling, and credential providers.

## Retry and Backoff

### Exponential Backoff with Jitter

```typescript
async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> {
  let attempt = 0

  while (attempt <= maxRetries) {
    try {
      return await fn()
    } catch (error) {
      attempt++
      if (attempt > maxRetries || !isRetryable(error)) throw error

      const exponential = Math.min(1000 * Math.pow(2, attempt - 1), 10000)
      const jitter = Math.random() * 500
      await sleep(exponential + jitter)
    }
  }
}

function isRetryable(error: any): boolean {
  return (
    error.code === 'ECONNRESET' ||
    error.code === 'ETIMEDOUT' ||
    (error.status >= 500 && error.status < 600) ||
    error.status === 429
  )
}
```

**Retry Decision Matrix:**

| Error Type | Retry? | Rationale |
|------------|--------|-----------|
| 5xx, 429, Network Timeout | ✅ Yes | Transient errors |
| 4xx, 401, 403, 404 | ❌ No | Client errors won't fix themselves |

### Rate Limit Handling

```typescript
if (error.status === 429) {
  const retryAfter = parseInt(error.headers['retry-after'] || '60')
  await sleep(retryAfter * 1000)
}
```

Respect `Retry-After` header on 429 responses.

See `references/retry-backoff.md` for jitter strategies, circuit breakers, and idempotency keys.

## Error Handling

### Typed Error Hierarchy

```typescript
class APIError extends Error {
  constructor(
    message: string,
    public status: number,
    public code: string,
    public requestId: string
  ) {
    super(message)
    this.name = 'APIError'
  }
}

class RateLimitError extends APIError {
  constructor(message: string, requestId: string, public retryAfter: number) {
    super(message, 429, 'rate_limit_error', requestId)
  }
}

class AuthenticationError extends APIError {
  constructor(message: string, requestId: string) {
    super(message, 401, 'authentication_error', requestId)
  }
}
```

### Error Handling in Practice

```typescript
try {
  const user = await client.users.create({ email: 'invalid' })
} catch (error) {
  if (error instanceof RateLimitError) {
    await sleep(error.retryAfter * 1000)
  } else if (error instanceof AuthenticationError) {
    console.error('Invalid API key')
  } else if (error instanceof APIError) {
    console.error(`${error.message} (Request ID: ${error.requestId})`)
  }
}
```

Include request ID in all errors for debugging.

See `references/error-handling.md` for user-friendly messages, validation errors, and debugging support.

## Pagination

### Async Iterators (Recommended)

**TypeScript:**

```typescript
for await (const user of client.users.list({ limit: 100 })) {
  console.log(user.id, user.email)
}
```

**Python:**

```python
async for user in client.users.list(limit=100):
    print(user.id, user.email)
```

SDK automatically fetches next page.

### Implementation

```typescript
class UsersResource {
  async *list(options?: { limit?: number }): AsyncGenerator<User> {
    let cursor: string | undefined = undefined

    while (true) {
      const response = await this.client.request('GET', '/users', {
        query: { limit: String(options?.limit || 100), ...(cursor ? { cursor } : {}) }
      })

      for (const user of response.data) yield user

      if (!response.has_more) break
      cursor = response.next_cursor
    }
  }
}
```

### Manual Pagination

```typescript
let cursor: string | undefined = undefined
while (true) {
  const response = await client.users.list({ limit: 100, cursor })
  for (const user of response.data) console.log(user.id)
  if (!response.has_more) break
  cursor = response.next_cursor
}
```

Provide both automatic and manual options.

See `references/pagination.md` for cursor vs. offset pagination and Go channel patterns.

## Streaming

### Server-Sent Events

```typescript
async *stream(path: string, body?: any): AsyncGenerator<any> {
  const response = await fetch(url, {
    headers: { 'Accept': 'text/event-stream' },
    body: JSON.stringify(body)
  })

  const reader = response.body!.getReader()
  const decoder = new TextDecoder()

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    for (const line of chunk.split('\n')) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6)
        if (data === '[DONE]') return
        yield JSON.parse(data)
      }
    }
  }
}

// Usage
for await (const chunk of client.posts.stream({ prompt: 'Write a story' })) {
  process.stdout.write(chunk.content)
}
```

## Idempotency Keys

Prevent duplicate operations during retries:

```typescript
import { randomUUID } from 'crypto'

if (['POST', 'PATCH', 'PUT'].includes(method)) {
  headers['Idempotency-Key'] = options?.idempotencyKey || randomUUID()
}

// Usage
await client.charges.create(
  { amount: 1000 },
  { idempotencyKey: 'charge_unique_123' }
)
```

Server deduplicates requests by key.

## Versioning

### Semantic Versioning

- `1.0.0` → `1.1.0`: New features (safe)
- `1.1.0` → `2.0.0`: Breaking changes (review)
- `1.0.0` → `1.0.1`: Bug fixes (safe)

### Deprecation Warnings

```typescript
function deprecated(message: string, since: string) {
  return function (target: any, proper
Files: 18
Size: 108.0 KB
Complexity: 62/100
Category: Design

Related in Design