Design SDKs that developers love to use—APIs that feel native, error messages that guide, and experiences that reduce friction. This skill covers creating SDKs that drive adoption through exceptional developer experience rather than aggressive marketing. Trigger phrases: "SDK design",...
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Design SDKs that developers love to use—APIs that feel native, error messages that guide, and experiences that reduce friction. This skill covers creating SDKs that drive adoption through exceptional developer experience rather than aggressive marketing. Trigger phrases: "SDK design",...
Use this skill when you need design SDKs that developers love to use—APIs that feel native, error messages that guide, and experiences that reduce friction. This skill covers creating SDKs that drive adoption through exceptional developer experience rather than aggressive marketing. Trigger phrases: "SDK design",...
The best SDK marketing is an SDK that developers can't stop talking about. When your SDK makes developers feel productive and competent, they become your advocates. When it frustrates them, no amount of marketing will save you.
Overview
SDK developer experience (DX) encompasses everything a developer feels when using your library:
Discovery: How easily can they find and install it?
Learning: How quickly can they understand how to use it?
Using: How productive are they day-to-day?
Debugging: How easily can they fix problems?
Upgrading: How painlessly can they adopt new versions?
Great SDK DX is a competitive advantage. Developers choose tools that make them feel smart.
Before You Start
Review the developer-audience-context skill to understand:
What languages and frameworks do your target developers use?
What IDE/editor setups are most common?
What's their experience level with your problem domain?
What competing SDKs have they used? What do they like/dislike?
SDK design decisions should flow from deep understanding of your users.
API Design Principles
Principle 1: Optimize for the Common Case
The most frequent use case should require the least code.
Good Design:
# Common case: send a simple message
client.messages.send("Hello world", to="+1234567890")
# Full control when needed
client.messages.send(
body="Hello world",
to="+1234567890",
from_="+0987654321",
status_callback="https://...",
media_urls=["https://..."]
)
Bad Design:
# Every call requires full configuration
message = Message(
body=,
to=PhoneNumber(),
from_=PhoneNumber(config.get_default_from()),
options=MessageOptions(
status_callback=,
media_urls=[]
)
)
client.messages.send(message)
"Hello world"
"+1234567890"
None
Principle 2: Progressive Disclosure
Start simple, reveal complexity as needed.
// Level 1: Simplest possible usageconst result = await client.analyze("Hello world");
// Level 2: Common optionsconst result = await client.analyze("Hello world", {
language: "en",
features: ["sentiment", "entities"]
});
// Level 3: Full controlconst result = await client.analyze("Hello world", {
language: "en",
features: ["sentiment", "entities"],
model: "v2-large",
timeout: 30000,
retries: { max: 3, backoff: "exponential" }
});
Principle 3: Fail Fast and Clearly
Catch errors as early as possible, with actionable messages.
Good:
# Validation at construction time
client = MyClient(api_key="")
# Raises immediately: ValueError: API key cannot be empty.# Get your API key at https://dashboard.example.com/keys# Clear error at runtime
client.users.get("invalid-id")
# Raises: NotFoundError: User 'invalid-id' not found.# Use client.users.list() to see available users.
Bad:
client = MyClient(api_key="") # No validation
result = client.users.get("invalid-id")
# Returns: None (is this an error? empty result? who knows?)# Or worse: raises generic Exception with stack trace
Principle 4: Sensible Defaults
Default values should work for most cases without configuration.
// This should just work without configurationconst client = newMyClient({ apiKey: process.env.MY_API_KEY });
// Sensible defaults:// - Automatic retries with exponential backoff// - Reasonable timeouts// - JSON content type// - Standard auth headers// - Connection pooling
Error Messages That Guide
Error messages are documentation. Make them helpful.
The Error Message Framework
Every error message should answer:
What happened?
Why did it happen?
How do I fix it?
Good vs. Bad Error Messages
Good:
AuthenticationError: Invalid API key provided.
The API key 'sk_test_abc...' (test key) cannot be used for
production requests.
To fix this:
1. Go to https://dashboard.example.com/keys
2. Copy your production API key (starts with 'sk_live_')
3. Update your environment variable: MY_API_KEY=sk_live_...
Docs: https://docs.example.com/authentication
Bad:
Error: 401 Unauthorized
Error Types to Distinguish
Create specific error types that developers can catch:
from myapi.errors import (
AuthenticationError, # Invalid/missing credentials
AuthorizationError, # Valid creds, insufficient permissions
ValidationError, # Invalid input data
NotFoundError, # Resource doesn't exist
RateLimitError, # Too many requests
ServerError, # Our fault, retry might help
)
try:
client.users.get(user_id)
except NotFoundError as e:
# Handle missing user specificallyexcept AuthenticationError as e:
# Handle auth issues specificallyexcept MyAPIError as e:
# Catch-all for other API errors
Type safety is documentation that never goes stale.
TypeScript Best Practices
// Define explicit types for all inputs and outputsinterfaceUser {
id: string;
email: string;
name: string;
createdAt: Date;
metadata?: Record<string, unknown>;
}
interfaceCreateUserInput {
email: string;
name: string;
metadata?: Record<string, unknown>;
}
// Return types are explicitasyncfunctioncreateUser(input: CreateUserInput): Promise<User> {
// ...
}
// Use discriminated unions for responsestypeApiResponse<T> =
| { success: true; data: T }
| { success: false; error: ApiError };
Autocomplete-Driven Design
Design for IDE autocomplete:
// Good: autocomplete shows all options
client.messages.create({
to: "+1...", // IDE shows: (property) to: stringbody: "...", // IDE shows: (property) body: string// User types 'me' and sees 'mediaUrls' autocomplete
});
// Bad: requires memorization
client.send("messages", { /* what goes here? */ });
Enum and Literal Types
// Good: constrained values with autocompletetypeMessageStatus = "queued" | "sending" | "sent" | "failed";
interfaceMessage {
status: MessageStatus; // IDE shows valid values
}
// Bad: any string acceptedinterfaceMessage {
status: string; // No guidance, errors at runtime
}
IDE Integration
Make Discovery Easy
Structure your SDK so IDE features help developers:
// Namespace methods logically
client.users.get(id)
client.users.list()
client.users.create(data)
client.users.update(id, data)
client.users.delete(id)
// After typing 'client.users.' the IDE shows all user operations
JSDoc/Docstrings Everywhere
/**
* Creates a new user in your organization.
*
* @paraminput - The user details
* @param input.email - Must be a valid email address
* @param input.name - Display name (max 100 characters)
* @returns The created user with generated ID
* @throws {ValidationError} If email format is invalid
* @throws {ConflictError} If email already exists
*
* @example
* const user = await client.users.create({
* email: "jane@example.com",
* name: "Jane Developer"
* });
*/asynccreateUser(input: CreateUserInput): Promise<User>
Inline Examples
defsend_message(self, body: str, to: str, **kwargs) -> Message:
"""
Send an SMS message.
Args:
body: The message content (max 1600 characters)
to: Recipient phone number in E.164 format
Returns:
Message object with ID and status
Example:
>>> message = client.messages.send(
... body="Hello from Python!",
... to="+14155551234"
... )
>>> print(message.status)
'queued'
"""
import warnings
defold_method(self):
"""
.. deprecated:: 2.3.0
Use :meth:`new_method` instead. Will be removed in 3.0.0.
"""
warnings.warn(
"old_method() is deprecated, use new_method() instead. ""See migration guide: https://docs.example.com/migrate-v3",
DeprecationWarning,
stacklevel=2
)
returnself.new_method()
Migration Guides
Migration Guide Structure
# Migrating from v2 to v3## Overview
Version 3 introduces [major change] and removes [deprecated feature].
Migration typically takes [time estimate].
## Breaking Changes### 1. Client Initialization**Before (v2):**```python
client = MyClient(key="...")
After (v3):
client = MyClient(api_key="...")
Why: Consistency with other SDK parameters.
2. [Next breaking change]
...
Deprecated Features Removed
client.old_method() - Use client.new_method() instead
LegacyClass - Use ModernClass instead
New Features
[Feature that makes migration worthwhile]
Need Help?
[Migration support channel]
[Office hours for migration questions]
### Codemods and Automation
When possible, provide automated migration:
```bash
# Provide migration scripts
npx @myapi/migrate-v3
# Or codemods
npx jscodeshift -t @myapi/codemods/v2-to-v3 src/
Making SDKs Feel Native
Language Idioms
Python: Use snake_case, context managers, generators
# Pythonicwith client.batch() as batch:
for user in client.users.list():
batch.add(user.send_notification("Hello"))
# Not Pythonic
users = client.getUsers()
batch = client.createBatch()
for i inrange(len(users)):
batch.addOperation(users[i].sendNotification("Hello"))
batch.execute()
JavaScript: Use Promises, async/await, destructuring