$npx -y skills add jonathimer/devmarketing-skills --skill sdk-dxDesign 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 des
| 1 | # SDK Design and Developer Experience |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | SDK developer experience (DX) encompasses everything a developer feels when using your library: |
| 8 | - **Discovery**: How easily can they find and install it? |
| 9 | - **Learning**: How quickly can they understand how to use it? |
| 10 | - **Using**: How productive are they day-to-day? |
| 11 | - **Debugging**: How easily can they fix problems? |
| 12 | - **Upgrading**: How painlessly can they adopt new versions? |
| 13 | |
| 14 | Great SDK DX is a competitive advantage. Developers choose tools that make them feel smart. |
| 15 | |
| 16 | ## Before You Start |
| 17 | |
| 18 | Review the **developer-audience-context** skill to understand: |
| 19 | - What languages and frameworks do your target developers use? |
| 20 | - What IDE/editor setups are most common? |
| 21 | - What's their experience level with your problem domain? |
| 22 | - What competing SDKs have they used? What do they like/dislike? |
| 23 | |
| 24 | SDK design decisions should flow from deep understanding of your users. |
| 25 | |
| 26 | ## API Design Principles |
| 27 | |
| 28 | ### Principle 1: Optimize for the Common Case |
| 29 | |
| 30 | The most frequent use case should require the least code. |
| 31 | |
| 32 | **Good Design:** |
| 33 | ```python |
| 34 | # Common case: send a simple message |
| 35 | client.messages.send("Hello world", to="+1234567890") |
| 36 | |
| 37 | # Full control when needed |
| 38 | client.messages.send( |
| 39 | body="Hello world", |
| 40 | to="+1234567890", |
| 41 | from_="+0987654321", |
| 42 | status_callback="https://...", |
| 43 | media_urls=["https://..."] |
| 44 | ) |
| 45 | ``` |
| 46 | |
| 47 | **Bad Design:** |
| 48 | ```python |
| 49 | # Every call requires full configuration |
| 50 | message = Message( |
| 51 | body="Hello world", |
| 52 | to=PhoneNumber("+1234567890"), |
| 53 | from_=PhoneNumber(config.get_default_from()), |
| 54 | options=MessageOptions( |
| 55 | status_callback=None, |
| 56 | media_urls=[] |
| 57 | ) |
| 58 | ) |
| 59 | client.messages.send(message) |
| 60 | ``` |
| 61 | |
| 62 | ### Principle 2: Progressive Disclosure |
| 63 | |
| 64 | Start simple, reveal complexity as needed. |
| 65 | |
| 66 | ```javascript |
| 67 | // Level 1: Simplest possible usage |
| 68 | const result = await client.analyze("Hello world"); |
| 69 | |
| 70 | // Level 2: Common options |
| 71 | const result = await client.analyze("Hello world", { |
| 72 | language: "en", |
| 73 | features: ["sentiment", "entities"] |
| 74 | }); |
| 75 | |
| 76 | // Level 3: Full control |
| 77 | const result = await client.analyze("Hello world", { |
| 78 | language: "en", |
| 79 | features: ["sentiment", "entities"], |
| 80 | model: "v2-large", |
| 81 | timeout: 30000, |
| 82 | retries: { max: 3, backoff: "exponential" } |
| 83 | }); |
| 84 | ``` |
| 85 | |
| 86 | ### Principle 3: Fail Fast and Clearly |
| 87 | |
| 88 | Catch errors as early as possible, with actionable messages. |
| 89 | |
| 90 | **Good:** |
| 91 | ```python |
| 92 | # Validation at construction time |
| 93 | client = MyClient(api_key="") |
| 94 | # Raises immediately: ValueError: API key cannot be empty. |
| 95 | # Get your API key at https://dashboard.example.com/keys |
| 96 | |
| 97 | # Clear error at runtime |
| 98 | client.users.get("invalid-id") |
| 99 | # Raises: NotFoundError: User 'invalid-id' not found. |
| 100 | # Use client.users.list() to see available users. |
| 101 | ``` |
| 102 | |
| 103 | **Bad:** |
| 104 | ```python |
| 105 | client = MyClient(api_key="") # No validation |
| 106 | result = client.users.get("invalid-id") |
| 107 | # Returns: None (is this an error? empty result? who knows?) |
| 108 | # Or worse: raises generic Exception with stack trace |
| 109 | ``` |
| 110 | |
| 111 | ### Principle 4: Sensible Defaults |
| 112 | |
| 113 | Default values should work for most cases without configuration. |
| 114 | |
| 115 | ```javascript |
| 116 | // This should just work without configuration |
| 117 | const client = new MyClient({ apiKey: process.env.MY_API_KEY }); |
| 118 | |
| 119 | // Sensible defaults: |
| 120 | // - Automatic retries with exponential backoff |
| 121 | // - Reasonable timeouts |
| 122 | // - JSON content type |
| 123 | // - Standard auth headers |
| 124 | // - Connection pooling |
| 125 | ``` |
| 126 | |
| 127 | ## Error Messages That Guide |
| 128 | |
| 129 | Error messages are documentation. Make them helpful. |
| 130 | |
| 131 | ### The Error Message Framework |
| 132 | |
| 133 | Every error message should answer: |
| 134 | 1. **What** happened? |
| 135 | 2. **Why** did it happen? |
| 136 | 3. **How** do I fix it? |
| 137 | |
| 138 | ### Good vs. Bad Error Messages |
| 139 | |
| 140 | **Good:** |
| 141 | ``` |
| 142 | AuthenticationError: Invalid API key provided. |
| 143 | |
| 144 | The API key 'sk_test_abc...' (test key) cannot be used for |
| 145 | production requests. |
| 146 | |
| 147 | To fix this: |
| 148 | 1. Go to https://dashboard.example.com/keys |
| 149 | 2. Copy your production API key (starts with 'sk_live_') |
| 150 | 3. Update your environment variable: MY_API_KEY=sk_live_... |
| 151 | |
| 152 | Docs: https://docs.example.com/authentication |
| 153 | ``` |
| 154 | |
| 155 | **Bad:** |
| 156 | ``` |
| 157 | Error: 401 Unauthorized |
| 158 | ``` |
| 159 | |
| 160 | ### Error Types to Distinguish |
| 161 | |
| 162 | Create specific error types that developers can catch: |
| 163 | |
| 164 | ```python |
| 165 | from myapi.errors import ( |
| 166 | AuthenticationError, # Invalid/missing credentials |
| 167 | AuthorizationError, # |