When API adoption lags, /platform-product-manager shapes developer experience, so you can grow platform engagement. — Claude Skill
A Claude Skill for Claude Code by Nick Jensen — run /platform-product-manager in Claude·Updated
Design APIs, plan SDK strategy, and build developer marketplaces
- Audit API surface area and flag inconsistencies in naming and versioning
- Design SDK rollout plans across 3-5 language ecosystems
- Structure API versioning policies with deprecation timelines
- Plan developer marketplace economics with revenue-share models
- Score developer experience across onboarding, docs, error messages, and sandbox quality
Who this is for
What it does
Run /platform-product-manager to audit 25 endpoints for naming consistency, REST convention compliance, error schema standards, and pagination patterns — producing a fix-list ranked by developer friction.
Use /platform-product-manager to plan SDK releases for Python, Node, and Go: scope each SDK at core vs full, estimate build effort (4-8 weeks per SDK), and sequence by developer segment size.
Run /platform-product-manager to score DX across 6 dimensions: time-to-first-call, docs completeness, error clarity, sandbox fidelity, authentication friction, and community support — each rated 1-10 with improvement actions.
Use /platform-product-manager to design a developer marketplace: listing requirements, review process, revenue split (e.g., 80/20), discovery algorithm inputs, and launch targets for first 20 integrations.
How it works
Describe your platform: API type (REST, GraphQL, gRPC), current endpoint count, developer audience, and the platform challenge you face.
The skill evaluates your API design, developer onboarding flow, and ecosystem strategy against platform PM best practices.
It identifies gaps in versioning policy, SDK coverage, documentation, and marketplace mechanics with concrete recommendations.
You receive a platform strategy document with API audit findings, DX scorecard, and a phased roadmap for ecosystem growth.
Example
REST API with 40 endpoints, 200 active API consumers, no SDKs yet. Time-to-first-call is 45 minutes. We want to grow to 1000 consumers this year. Where do we start?
Time-to-first-call: 45min (target: <10min). Root causes: OAuth2 setup requires 6 steps, no sandbox environment, API key generation buried in settings. Quick win: add API key auth for sandbox, cuts TTFC to 8min.
Launch order: 1. Python SDK (48% of consumers use Python, 5-week build). 2. Node SDK (31%, 4-week build). 3. Go SDK (12%, 6-week build). Each SDK should include auth helpers, typed models, and retry logic.
Phase 1 (months 1-2): Sandbox + API key auth + Python SDK = target 400 consumers. Phase 2 (months 3-4): Node SDK + interactive docs + webhook simulator = target 700. Phase 3 (months 5-6): Marketplace beta with 10 partner integrations = target 1000.
Metrics this improves
Works with
Platform Product Manager
Strategic product management expertise for API-first and developer-focused platforms — from API design and developer experience to ecosystem building and platform metrics.
Philosophy
Great platform products aren't about features. They're about making developers successful.
The best API and platform products:
- Developer experience is product experience — DX is your primary differentiator
- APIs are user interfaces — Design them with the same care as visual UIs
- Documentation is product — Great docs reduce support, increase adoption, drive success
- Ecosystem multiplies value — Your integrations make your platform stickier
How This Skill Works
When invoked, apply the guidelines in rules/ organized by:
api-*— API design principles, standards, and patternsdx-*— Developer experience, onboarding, and successdocs-*— Developer documentation strategy and standardssdk-*— SDK and library strategyversioning-*— API versioning, deprecation, and migrationcommunity-*— Developer community and ecosystem buildingmarketplace-*— Integration marketplace and partner strategymetrics-*— Platform health and success metrics
Core Frameworks
Platform Maturity Model
| Stage | Focus | Key Metrics | Team Structure |
|---|---|---|---|
| Foundation | Core API, basic docs | API uptime, error rates | PM + Engineers |
| Growth | DX, SDKs, onboarding | Time-to-first-call, activation | + DevRel, DX engineers |
| Scale | Ecosystem, marketplace | Integration count, partner revenue | + Partner team |
| Platform | Network effects, flywheel | Platform GMV, ecosystem value | Full platform org |
The Developer Journey
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DISCOVER │ → │ EVALUATE │ → │ ADOPT │ → │ EXPAND │
│ │ │ │ │ │ │ │
│ - Search │ │ - Docs │ │ - Signup │ │ - More APIs │
│ - Content │ │ - Sandbox │ │ - First call│ │ - Higher │
│ - Referral │ │ - Pricing │ │ - Use case │ │ volume │
│ │ │ │ │ solved │ │ - Referral │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
API Design Hierarchy
┌─────────────────┐
│ CONSISTENCY │ ← Predictable patterns
├─────────────────┤
│ SIMPLICITY │ ← Easy to understand
├─────────────────┤
│ DISCOVERABILITY│ ← Self-documenting
├─────────────────┤
│ RELIABILITY │ ← Stable and trustworthy
├─────────────────┤
│ PERFORMANCE │ ← Fast and efficient
└─────────────────┘
Developer Success Metrics Stack
┌─────────────────────────────────────────────────────────────────┐
│ BUSINESS OUTCOMES │
│ Revenue, Retention, Net Dollar Retention │
├─────────────────────────────────────────────────────────────────┤
│ DEVELOPER SUCCESS │
│ Active Developers, API Calls, Use Cases Completed │
├─────────────────────────────────────────────────────────────────┤
│ DEVELOPER EXPERIENCE │
│ Time-to-First-Call, Activation Rate, Support Tickets │
├─────────────────────────────────────────────────────────────────┤
│ PLATFORM HEALTH │
│ Uptime, Latency, Error Rates, Documentation Quality │
└─────────────────────────────────────────────────────────────────┘
Platform Types
| Type | Examples | Key Success Factor | Primary Metric |
|---|---|---|---|
| Infrastructure API | Stripe, Twilio, AWS | Reliability + DX | API calls, uptime |
| Data API | Plaid, Clearbit | Data quality + freshness | Data coverage |
| Aggregation Platform | Zapier, Segment | Integrations + ease | Connections made |
| Developer Tools | GitHub, Vercel | Workflow fit + speed | Active projects |
| Embedded Platform | Shopify Apps | Distribution + value | Install rate, GMV |
API Style Comparison
| Style | Best For | Complexity | Flexibility | Caching |
|---|---|---|---|---|
| REST | CRUD operations, simple resources | Low | Medium | Excellent |
| GraphQL | Complex data fetching, mobile | Medium | High | Manual |
| gRPC | Internal services, high perf | High | Low | N/A |
| Webhooks | Real-time events, async flows | Low | Medium | N/A |
| WebSocket | Bi-directional, real-time | Medium | High | N/A |
Anti-Patterns
- API-first without developer-first — Technically great API that's hard to use
- Documentation as afterthought — Docs written after API is "done"
- Breaking changes without warning — Surprising developers with incompatibilities
- Vanity integrations — Building integrations nobody uses for marketing
- Platform before product-market fit — Building ecosystem before core value
- Ignoring support signals — Not treating support tickets as product feedback
- One-size-fits-all SDK — Same SDK strategy for all languages/use cases
- Versioning without migration path — New versions without upgrade guides
Reference documents
title: Section Organization
1. API Design (api)
Impact: CRITICAL Description: API design principles, standards, and patterns. The foundation of your developer experience.
2. Developer Experience (dx)
Impact: CRITICAL Description: Developer onboarding, time-to-value, and overall experience. Your primary differentiator.
3. Documentation Strategy (docs)
Impact: HIGH Description: Developer documentation structure, quality standards, and maintenance. Docs are product.
4. SDK Strategy (sdk)
Impact: HIGH Description: SDK and library strategy, language prioritization, and maintenance approaches.
5. Versioning & Deprecation (versioning)
Impact: HIGH Description: API versioning strategies, breaking change management, and deprecation processes.
6. Developer Community (community)
Impact: MEDIUM-HIGH Description: Developer community building, engagement, and ecosystem development.
7. Integration Marketplace (marketplace)
Impact: MEDIUM Description: Partner integrations, marketplace strategy, and ecosystem expansion.
8. Platform Metrics (metrics)
Impact: CRITICAL Description: Platform health metrics, developer success measurement, and data-driven improvement.
title: API Design Principles impact: CRITICAL tags: api, design, rest, graphql, standards
API Design Principles
Impact: CRITICAL
APIs are user interfaces for developers. Design them with the same rigor and care you'd apply to visual UIs.
The Five Pillars of API Design
| Pillar | Description | Example |
|---|---|---|
| Consistency | Same patterns everywhere | Always use created_at not sometimes createdAt |
| Predictability | Developers guess correctly | GET /users/{id} returns user, not array |
| Discoverability | Self-documenting | Clear naming, HATEOAS links |
| Reliability | Behaves as expected | Same input = same output |
| Simplicity | Easy to understand | Minimal parameters for common cases |
Resource Naming Conventions
Good resource naming:
GET /users # List users
GET /users/{id} # Get single user
POST /users # Create user
PATCH /users/{id} # Update user
DELETE /users/{id} # Delete user
GET /users/{id}/orders # User's orders (nested resource)
GET /orders/{id} # Order by ID (top-level access)
Bad resource naming:
GET /getUsers # Verb in URL
GET /user/{id} # Inconsistent singular/plural
POST /users/create # Redundant action
GET /users/{id}/getOrders # Verb in nested resource
GET /api/v1/Users # Inconsistent casing
HTTP Methods Matrix
| Method | Idempotent | Safe | Use For |
|---|---|---|---|
| GET | Yes | Yes | Reading resources |
| POST | No | No | Creating resources, actions |
| PUT | Yes | No | Full resource replacement |
| PATCH | No* | No | Partial updates |
| DELETE | Yes | No | Removing resources |
*PATCH can be idempotent if designed carefully
Response Envelope Pattern
Consistent response structure:
{
"data": {
"id": "usr_123",
"email": "[email protected]",
"created_at": "2024-01-15T10:30:00Z"
},
"meta": {
"request_id": "req_abc123"
}
}
List responses with pagination:
{
"data": [
{ "id": "usr_123", "email": "[email protected]" },
{ "id": "usr_456", "email": "[email protected]" }
],
"meta": {
"total_count": 150,
"page": 1,
"per_page": 20,
"has_more": true
},
"links": {
"self": "/users?page=1",
"next": "/users?page=2",
"last": "/users?page=8"
}
}
Error Response Design
Good error responses:
{
"error": {
"code": "validation_error",
"message": "The request body contains invalid fields",
"details": [
{
"field": "email",
"code": "invalid_format",
"message": "Email must be a valid email address"
},
{
"field": "amount",
"code": "out_of_range",
"message": "Amount must be between 1 and 10000"
}
],
"doc_url": "https://docs.example.com/errors/validation_error",
"request_id": "req_abc123"
}
}
Bad error responses:
{
"error": "Something went wrong"
}
{
"success": false,
"message": "Invalid input"
}
{
"code": 400
}
ID Design Principles
| Approach | Pros | Cons | Best For |
|---|---|---|---|
Prefixed IDs (usr_123) | Type-safe, debuggable | Slightly longer | External APIs |
| UUIDs | Globally unique, no sequence | Long, not human-readable | Distributed systems |
| Sequential integers | Short, simple | Guessable, leaks info | Internal systems |
| Hashids | Short, non-sequential | Adds dependency | URL shorteners |
Prefixed ID pattern (recommended):
usr_2cDnPmQTz4 # User
ord_8bKmLpNwQr # Order
txn_3gHjRsTvWx # Transaction
api_key_4nLmOpQrSt # API key
Request Parameter Guidelines
Query parameters for filtering/options:
GET /users?status=active&created_after=2024-01-01&limit=50
GET /orders?customer_id=cust_123&include=line_items
Path parameters for resource identification:
GET /users/{user_id}/orders/{order_id}
Request body for complex data:
POST /orders
{
"customer_id": "cust_123",
"line_items": [
{ "product_id": "prod_456", "quantity": 2 }
]
}
Idempotency Design
Idempotency key pattern:
POST /charges
Idempotency-Key: abc123-unique-key
{
"amount": 1000,
"currency": "usd"
}
Server handling:
- Check if idempotency key exists
- If exists, return cached response
- If new, process request and cache response
Field Naming Standards
| Convention | Example | Use Case |
|---|---|---|
| snake_case | created_at | Most REST APIs (recommended) |
| camelCase | createdAt | JavaScript-heavy ecosystems |
| Choose one and be consistent | Always |
Timestamp fields:
{
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:22:00Z",
"deleted_at": null
}
Anti-Patterns
- Inconsistent naming — Mixing
created_at,createdAt, andCreateDate - Verbs in URLs —
/users/getByIdinstead of/users/{id} - Ignoring HTTP semantics — Using POST for reads, GET for mutations
- Leaky abstractions — Exposing database schema in API structure
- No versioning strategy — Breaking changes without version control
- Missing request IDs — No way to trace requests across systems
- Opaque errors — Generic error messages without actionable details
- Inconsistent null handling — Sometimes omitting, sometimes
null
title: API Security and Authentication impact: CRITICAL tags: security, authentication, authorization, api-keys, oauth
API Security and Authentication
Impact: CRITICAL
Security is non-negotiable for APIs. One breach destroys developer trust permanently.
Authentication Methods Comparison
| Method | Security | UX | Best For |
|---|---|---|---|
| API Keys | Medium | Simple | Server-to-server |
| OAuth 2.0 | High | Complex | User authorization |
| JWT | Medium-High | Medium | Stateless auth |
| mTLS | Very High | Complex | Enterprise, high-security |
| Basic Auth | Low | Simple | Legacy, internal only |
API Key Design
Good API key format:
{prefix}_live_{random_string} (secret key, production)
{prefix}_test_{random_string} (secret key, test)
pk_live_{random_string} (publishable key, production)
pk_test_{random_string} (publishable key, test)
Example: acme_live_a1b2c3d4e5f6g7h8i9j0
Key components:
- Prefix: Type identifier (
sk_,pk_) - Environment:
liveortest - Random string: High entropy, 24-32 characters
API key management:
| Feature | Priority | Purpose |
|---|---|---|
| Key rotation | Critical | Change keys without downtime |
| Multiple keys | Critical | Different keys for different purposes |
| Key restrictions | High | IP, domain, scope limits |
| Usage tracking | High | Audit, billing |
| Expiration | Medium | Time-limited access |
| Labels | Medium | Identify key purpose |
OAuth 2.0 Implementation
Common OAuth flows:
| Flow | Use Case | Security Level |
|---|---|---|
| Authorization Code + PKCE | Web apps, mobile | Highest |
| Client Credentials | Server-to-server | High |
| Device Code | CLIs, TVs | Medium |
| Implicit (deprecated) | Legacy SPAs | Low - Avoid |
Authorization code flow:
┌──────────┐ ┌──────────┐
│ Client │ │ Auth │
│ App │ │ Server │
└────┬─────┘ └────┬─────┘
│ │
│ 1. Redirect to /authorize │
│ ────────────────────────────────────────►│
│ │
│ 2. User authenticates │
│ │
│ 3. Redirect with authorization code │
│ ◄────────────────────────────────────────│
│ │
│ 4. Exchange code for token │
│ ────────────────────────────────────────►│
│ │
│ 5. Return access + refresh tokens │
│ ◄────────────────────────────────────────│
│ │
Token Best Practices
Access token design:
| Property | Recommendation |
|---|---|
| Format | JWT or opaque |
| Lifetime | 15 min - 1 hour |
| Storage | Memory only (client) |
| Revocation | Supported via blocklist |
| Scope | Minimal required permissions |
Refresh token design:
| Property | Recommendation |
|---|---|
| Lifetime | 7-30 days |
| Rotation | New refresh token on use |
| Storage | Secure, encrypted |
| Revocation | Immediate on logout |
Scopes and Permissions
Good scope design:
read:users # Read user data
write:users # Create/update users
delete:users # Delete users
read:orders # Read orders
write:orders # Create orders
admin:orders # Full order access
read:* # Read all resources
admin:* # Full access
Scope hierarchy:
admin:users
│
├── write:users
│ │
│ └── read:users
│
└── delete:users
Rate Limiting Security
Rate limit by:
| Dimension | Purpose | Example |
|---|---|---|
| API key | Prevent abuse per customer | 1000 req/min |
| IP address | Prevent distributed attacks | 100 req/min |
| Endpoint | Protect expensive operations | 10 req/min for /search |
| User | Fair usage per user | 100 req/min |
Rate limit headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Webhook Security
Webhook signature verification:
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# Usage
is_valid = verify_webhook(
payload=request.body,
signature=request.headers['X-Signature'],
secret=webhook_secret
)
Webhook security checklist:
- HMAC signature on all webhooks
- Timestamp validation (prevent replay)
- HTTPS endpoints only
- Retry with exponential backoff
- Idempotency support
- IP allowlist option
Security Headers
Required response headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'none'
Cache-Control: no-store
Input Validation
Validate all inputs:
| Input Type | Validation |
|---|---|
| Strings | Max length, character set, sanitization |
| Numbers | Min/max range, type |
| Emails | Format, domain validation |
| URLs | Protocol whitelist, format |
| IDs | Format, existence |
| Arrays | Max length, item validation |
| Objects | Schema validation |
Validation error response:
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "invalid_format",
"message": "Must be a valid email address"
}
]
}
}
Sensitive Data Handling
Never expose in responses:
- Full credit card numbers
- Passwords or password hashes
- API secret keys
- Internal IDs that leak information
- PII unless explicitly requested
Masking patterns:
{
"card": {
"last4": "4242",
"brand": "visa",
"exp_month": 12,
"exp_year": 2025
}
}
Security Logging
Log for security:
| Event | Log Level | Retention |
|---|---|---|
| Authentication failure | Warn | 90 days |
| Authorization failure | Warn | 90 days |
| Rate limit exceeded | Info | 30 days |
| Suspicious patterns | Warn | 180 days |
| Admin actions | Info | 1 year |
| Data access | Debug | 30 days |
Anti-Patterns
- API keys in URLs — Query params logged, cached, exposed
- No rate limiting — DoS vulnerability
- Long-lived tokens — Extended breach window
- Secrets in responses — Exposing keys in API responses
- HTTP allowed — No HTTPS enforcement
- No key rotation — Can't respond to compromises
- Hardcoded secrets — Keys in code/configs
- Overly broad scopes — All-or-nothing permissions
- Missing audit logs — Can't investigate incidents
- Trust client input — No server-side validation
title: Webhooks and Event-Driven APIs impact: HIGH tags: webhooks, events, async, real-time, notifications
Webhooks and Event-Driven APIs
Impact: HIGH
Webhooks turn your API from request-response into a real-time platform. They're how developers build reactive integrations.
Webhook vs Polling Comparison
| Factor | Webhooks | Polling |
|---|---|---|
| Latency | Real-time (seconds) | Minutes to hours |
| Efficiency | Only when changes occur | Constant requests |
| Complexity | Endpoint setup required | Simple GET requests |
| Reliability | Needs retry logic | Simpler recovery |
| Cost | Lower for provider | Higher API usage |
Event Naming Conventions
Good event naming:
{resource}.{action}
customer.created
customer.updated
customer.deleted
order.placed
order.fulfilled
order.cancelled
payment.succeeded
payment.failed
payment.refunded
subscription.created
subscription.upgraded
subscription.cancelled
Event naming rules:
- Use resource.action format
- Past tense for completed actions
- Lowercase, dot-separated
- Be specific (not generic
data.changed)
Webhook Payload Structure
Complete webhook payload:
{
"id": "evt_1NqINN2eZvKYlo2C4LsR",
"object": "event",
"type": "customer.created",
"created": 1705312200,
"api_version": "2024-01-01",
"data": {
"object": {
"id": "cus_NqINNKdSBqHhzG",
"object": "customer",
"email": "[email protected]",
"name": "Jane Doe",
"created": 1705312200
},
"previous_attributes": null
},
"request": {
"id": "req_abc123",
"idempotency_key": "key_xyz789"
},
"livemode": true
}
Essential payload fields:
| Field | Purpose |
|---|---|
| id | Unique event identifier |
| type | Event type (resource.action) |
| created | Unix timestamp of event |
| api_version | API version that generated event |
| data.object | Full resource state after change |
| data.previous_attributes | Changed fields (for updates) |
| livemode | Production vs test |
Webhook Delivery Guarantees
Delivery semantics:
| Guarantee | Description | Implementation |
|---|---|---|
| At-least-once | Events may be delivered multiple times | Standard - require idempotent handling |
| At-most-once | Events delivered once or not at all | Not recommended |
| Exactly-once | Events delivered exactly once | Very hard - use at-least-once + idempotency |
Retry Strategy
Exponential backoff schedule:
| Attempt | Delay | Cumulative |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 30s | 30s |
| 3 | 5m | 5.5m |
| 4 | 30m | 35.5m |
| 5 | 2h | 2h 35.5m |
| 6 | 5h | 7h 35.5m |
| 7 | 10h | 17h 35.5m |
| 8 | 24h | 41h 35.5m |
After max retries:
- Mark webhook as failed
- Email notification to developer
- Available in dashboard for manual retry
Webhook Signature Verification
Signature header format:
POST /webhook HTTP/1.1
Content-Type: application/json
X-Signature: t=1705312200,v1=5257a869e...
X-Webhook-ID: wh_1NqINN2eZvKYlo2C
{webhook payload}
Verification steps:
import hmac
import hashlib
import time
def verify_signature(payload, header, secret):
# Parse signature header
parts = dict(p.split('=') for p in header.split(','))
timestamp = int(parts['t'])
signature = parts['v1']
# Check timestamp (prevent replay)
if abs(time.time() - timestamp) > 300: # 5 min tolerance
return False
# Compute expected signature
signed_payload = f"{timestamp}.{payload}"
expected = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Compare (timing-safe)
return hmac.compare_digest(expected, signature)
Webhook Endpoint Configuration
Configuration options to provide:
| Option | Description | Example |
|---|---|---|
| URL | Endpoint to receive webhooks | https://example.com/webhook |
| Events | Which events to receive | customer.*, payment.succeeded |
| Secret | Signing secret | whsec_abc123... |
| API version | Payload format version | 2024-01-01 |
| Enabled | Active/inactive toggle | true |
Webhook Dashboard Features
Essential dashboard views:
Webhooks
├── Endpoints
│ ├── Create endpoint
│ ├── Edit endpoint
│ └── Delete endpoint
│
├── Event logs
│ ├── Filter by event type
│ ├── Filter by status
│ ├── View payload
│ └── Manual retry
│
├── Testing
│ ├── Send test event
│ └── Local testing setup
│
└── Metrics
├── Delivery rate
├── Average latency
└── Failure reasons
Handling Webhook Best Practices
Good webhook handler:
@app.route('/webhook', methods=['POST'])
def handle_webhook():
payload = request.get_data(as_text=True)
sig_header = request.headers.get('X-Signature')
# 1. Verify signature
if not verify_signature(payload, sig_header, webhook_secret):
return 'Invalid signature', 400
event = json.loads(payload)
# 2. Check idempotency (already processed?)
if already_processed(event['id']):
return 'OK', 200
# 3. Route to handler
try:
if event['type'] == 'customer.created':
handle_customer_created(event['data']['object'])
elif event['type'] == 'payment.succeeded':
handle_payment_succeeded(event['data']['object'])
# ... more handlers
# 4. Mark as processed
mark_processed(event['id'])
except Exception as e:
# Log but return 200 to prevent retries for app errors
log_error(e, event)
return 'OK', 200
Handler rules:
- Respond quickly (< 30 seconds)
- Process asynchronously for heavy work
- Be idempotent
- Return 2xx for success
- Return 4xx/5xx only for issues you want retried
Event Ordering
Events may arrive out of order:
Actual order: Delivery order (possible):
1. order.created 1. order.created
2. order.updated 2. order.fulfilled
3. order.fulfilled 3. order.updated
Handling out-of-order events:
- Use
createdtimestamp for ordering - Include version/sequence numbers
- Design handlers to be order-independent
- Use
data.previous_attributesfor state comparison
Testing Webhooks
Local development options:
| Tool | Description |
|---|---|
| CLI forwarding | example listen --forward localhost:3000/webhook |
| ngrok | Expose local server to internet |
| Dashboard testing | Send test events from dashboard |
| Mock endpoints | Webhook.site, RequestBin |
Test event payload:
{
"id": "evt_test_123",
"type": "customer.created",
"data": {
"object": {
"id": "cus_test_123",
"email": "[email protected]"
}
},
"livemode": false
}
Event Documentation
Document each event type:
# customer.created
Occurs when a new customer is created.
## Payload
```json
{
"type": "customer.created",
"data": {
"object": {
"id": "cus_123",
"email": "[email protected]",
"name": "Jane Doe",
"created": 1705312200
}
}
}
Triggers
- API:
POST /v1/customers - Dashboard: Creating customer
- Checkout: New customer during checkout
Related events
customer.updatedcustomer.deleted
### Anti-Patterns
- **No signature verification** — Anyone can spoof webhooks
- **Synchronous processing** — Slow handlers cause timeouts
- **No idempotency** — Duplicate events cause duplicate actions
- **Returning errors for app bugs** — Causes infinite retries
- **No event logs** — Can't debug delivery issues
- **Missing events** — Not exposing important state changes
- **No test mode** — Hard to develop against
- **No manual retry** — Can't recover from failures
- **Undocumented events** — Developers don't know what to expect
- **Breaking payload changes** — Changing event structure without versioning
title: Developer Community Building impact: MEDIUM-HIGH tags: community, devrel, developer-relations, ecosystem
Developer Community Building
Impact: MEDIUM-HIGH
A thriving developer community is a moat. It provides feedback, creates content, answers questions, and drives adoption.
Community Maturity Stages
| Stage | Size | Focus | Key Activities |
|---|---|---|---|
| Seed | 0-100 | Find early adopters | Direct outreach, 1:1 calls |
| Early | 100-1K | Build core community | Discord/Slack, office hours |
| Growth | 1K-10K | Scale engagement | Champions program, events |
| Scale | 10K+ | Ecosystem flywheel | User groups, conferences |
Community Platform Selection
| Platform | Best For | Pros | Cons |
|---|---|---|---|
| Discord | Real-time, dev tools | Active, searchable | Can be noisy |
| Slack | Enterprise, B2B | Professional | Expensive at scale |
| GitHub Discussions | OSS, technical | Native to workflow | Limited engagement |
| Discourse | Long-form, async | Searchable, SEO | Slower paced |
| Stack Overflow | Q&A, SEO | High intent | Hard to seed |
Community Engagement Funnel
AWARENESS 100% ────────────────────────────────
│
▼
JOINED COMMUNITY 20% ────────────────
│
▼
LURKER → READER 15% ──────────────
│
▼
ASKED QUESTION 5% ─────
│
▼
ANSWERED QUESTION 2% ──
│
▼
CHAMPION 0.5% ─
Developer Champions Program
Champion tiers:
| Tier | Requirements | Benefits |
|---|---|---|
| Contributor | 5+ helpful answers | Badge, swag |
| Champion | 25+ answers, content creation | Early access, direct line |
| Ambassador | Speaking, major content | Sponsorship, co-marketing |
| Partner | Business integration | Revenue share, roadmap input |
Champion benefits to provide:
- Early access to features
- Direct Slack/Discord with team
- Conference speaking opportunities
- Co-marketing on content
- Swag and recognition
- Input on roadmap
Content Types for Community
| Content Type | Purpose | Frequency |
|---|---|---|
| Tutorials | Teach new skills | Weekly |
| Case studies | Show what's possible | Monthly |
| Release notes | Keep informed | Per release |
| AMAs | Build connection | Monthly |
| Showcases | Celebrate community | Weekly |
| Challenges | Drive engagement | Quarterly |
Community Response SLAs
| Channel | Target Response Time | Escalation |
|---|---|---|
| Discord/Slack | < 4 hours (business hours) | After 24h |
| GitHub Issues | < 24 hours | After 72h |
| Stack Overflow | < 48 hours | After 1 week |
| Forum | < 24 hours | After 72h |
| Twitter/X | < 2 hours | After 8h |
Community Health Metrics
| Metric | Formula | Target |
|---|---|---|
| Active members | Posted last 30 days | Growing |
| Response rate | Questions with answers | > 90% |
| Time to answer | Question → first response | < 4h |
| Champion ratio | Champions / total members | > 2% |
| Content creation | Community content / month | Growing |
| NPS | Community satisfaction | > 50 |
Developer Events Strategy
Event types by investment:
| Event | Cost | Reach | Depth |
|---|---|---|---|
| Webinars | Low | High | Low |
| Office hours | Low | Medium | High |
| Hackathons | Medium | Medium | High |
| Meetups | Medium | Low | High |
| Conferences | High | High | Medium |
| User conference | Very High | Medium | Very High |
Feedback Loop Structure
┌─────────────────────────────────────────────────┐
│ COMMUNITY FEEDBACK │
└──────────────────────┬──────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ TRIAGE/TAG │ │ RESPOND │
│ - Bug │ │ - Acknowledge │
│ - Feature │ │ - Timeline │
│ - Question │ │ - Workaround │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ PRODUCT INPUT │
│ - Prioritize │
│ - Roadmap │
│ - Close loop │
└─────────────────┘
Handling Negative Feedback
Response framework (HEARD):
| Step | Action | Example |
|---|---|---|
| Hear | Acknowledge the issue | "I understand this is frustrating" |
| Empathize | Show understanding | "I'd be frustrated too if..." |
| Apologize | Take responsibility | "We should have communicated better" |
| Resolve | Fix or workaround | "Here's what we're doing..." |
| Develop | Prevent recurrence | "We're adding alerts to prevent this" |
Community Moderation Guidelines
Rules to establish:
- Be respectful and inclusive
- Stay on topic
- No spam or self-promotion
- Search before asking
- Use appropriate channels
- No sharing of credentials/keys
Enforcement ladder:
- Friendly reminder
- Official warning
- Temporary mute (24h)
- Longer suspension (7d)
- Permanent ban
Developer Advocacy vs Support
| Developer Advocacy | Developer Support |
|---|---|
| Proactive engagement | Reactive help |
| Content creation | Ticket resolution |
| Community building | Individual issues |
| External-facing | Internal-facing |
| Scales through content | Scales through tooling |
| Measured by reach | Measured by resolution |
Anti-Patterns
- Ghost town — Community exists but no team engagement
- Corporate speak — Marketing language in developer spaces
- Ignoring criticism — Deleting negative feedback
- Overpromising — Committing to features without PM alignment
- Champion neglect — Not nurturing top contributors
- Metrics obsession — Optimizing vanity metrics over health
- One-way broadcast — Announcing without engaging
- Slow responses — Letting questions go unanswered
- No escalation path — Community can't reach decision-makers
title: Developer Documentation Strategy impact: HIGH tags: docs, documentation, reference, guides
Developer Documentation Strategy
Impact: HIGH
Documentation is product. Great docs reduce support burden, increase activation, and serve as your best marketing.
The Documentation Pyramid
┌─────────────────┐
│ TUTORIALS │ ← "I want to learn"
│ Learning-oriented
├─────────────────┤
│ HOW-TO GUIDES │ ← "I want to accomplish"
│ Task-oriented
├─────────────────┤
│ REFERENCE │ ← "I want to look up"
│ Information-oriented
├─────────────────┤
│ EXPLANATION │ ← "I want to understand"
│ Understanding-oriented
└─────────────────┘
Documentation Types Matrix
| Type | Purpose | Audience State | Example |
|---|---|---|---|
| Quickstart | First success | New, eager | "Make your first API call" |
| Tutorials | Learn concepts | Learning | "Building a payment flow" |
| How-to guides | Solve problems | Working | "Handle webhook retries" |
| API reference | Look up details | Building | "POST /v1/customers" |
| Concepts | Deep understanding | Exploring | "How authentication works" |
| Changelog | Track changes | Maintaining | "v2.3 release notes" |
API Reference Structure
Good API reference page:
# Create a Customer
Creates a new customer object.
## Endpoint
POST /v1/customers
## Authentication
Requires Bearer token with `customers:write` scope.
## Request Body
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| email | string | Yes | Customer's email address |
| name | string | No | Customer's full name |
| metadata | object | No | Key-value pairs for custom data |
## Example Request
```bash
curl https://api.example.com/v1/customers \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "name": "Jane Doe"}'
Response
{
"id": "cus_123abc",
"object": "customer",
"email": "[email protected]",
"name": "Jane Doe",
"created_at": "2024-01-15T10:30:00Z"
}
Errors
| Code | Description |
|---|---|
| 400 | Invalid email format |
| 401 | Invalid API key |
| 409 | Customer already exists |
### Code Sample Standards
**Multi-language tabs:**
```markdown
[curl] [Python] [Node.js] [Ruby] [Go] [Java]
```python
import example
client = example.Client("sk_test_...")
customer = client.customers.create(
email="[email protected]",
name="Jane Doe"
)
print(customer.id)
**Code sample requirements:**
- Complete and runnable
- Uses realistic values
- Shows response handling
- Includes error handling where relevant
- Uses SDK conventions (not raw HTTP)
### Documentation Site Architecture
docs.example.com/ ├── /quickstart # 5-minute guide ├── /tutorials/ # Learning paths │ ├── /basics │ └── /advanced ├── /guides/ # How-to guides │ ├── /authentication │ ├── /webhooks │ └── /error-handling ├── /api/ # API reference │ ├── /customers │ ├── /orders │ └── /webhooks ├── /sdks/ # SDK documentation │ ├── /python │ ├── /node │ └── /go ├── /concepts/ # Deep dives │ ├── /authentication │ └── /rate-limiting ├── /changelog # Version history └── /support # Help resources
### Documentation Quality Checklist
**Every page should have:**
- [ ] Clear title and purpose
- [ ] Working code examples
- [ ] Copy buttons on code blocks
- [ ] Last updated date
- [ ] Feedback mechanism
- [ ] Related pages links
- [ ] Search visibility
**Every code sample should:**
- [ ] Be tested and working
- [ ] Use latest SDK version
- [ ] Include necessary imports
- [ ] Show complete context
- [ ] Handle common errors
### Search and Navigation
**Good search features:**
- Full-text search across all docs
- Search suggestions/autocomplete
- Filters by doc type (guide, reference, etc.)
- Recent searches
- Popular searches
**Good navigation:**
- Sticky sidebar with hierarchy
- Breadcrumbs
- Previous/next page links
- On-page table of contents
- Related pages
### Documentation Maintenance
**Version synchronization:**
| Component | Update Trigger | Owner |
|-----------|----------------|-------|
| API reference | API changes | Engineering |
| Code samples | SDK releases | DevRel/DX |
| Guides | Feature launches | Product |
| Changelog | Every release | Release manager |
**Documentation debt signals:**
- Support tickets citing docs
- Developer complaints
- Outdated code samples
- Missing features in docs
- Broken links
### Metrics for Documentation
| Metric | What It Measures | Target |
|--------|------------------|--------|
| **Page views** | Reach | Trending up |
| **Time on page** | Engagement | 2-5 min |
| **Bounce rate** | Relevance | < 50% |
| **Search → no results** | Gaps | < 5% |
| **Support deflection** | Effectiveness | > 70% |
| **Feedback score** | Quality | > 4/5 |
### Writing Style Guidelines
**Do:**
- Use active voice
- Write short sentences
- Lead with the action
- Use consistent terminology
- Include examples liberally
**Don't:**
- Use jargon without explanation
- Assume prior knowledge
- Write walls of text
- Use vague language
- Mix tenses
**Example transformation:**
Bad: "The request body can be populated with a JSON object containing the various parameters that are documented below."
Good: "Send a JSON object with these parameters:"
### Anti-Patterns
- **Docs as afterthought** — Writing docs after API is "done"
- **Reference-only** — No tutorials or guides
- **Stale examples** — Code that doesn't work
- **No versioning** — Same docs for all API versions
- **PDF documentation** — Not searchable, not maintainable
- **Internal jargon** — Using terms developers don't know
- **Missing error docs** — No explanation of error codes
- **No feedback loop** — Can't report doc issues
title: Developer Onboarding impact: CRITICAL tags: dx, onboarding, activation, time-to-value
Developer Onboarding
Impact: CRITICAL
Time-to-first-API-call is your most important activation metric. Every minute of friction loses developers.
The Developer Onboarding Funnel
SIGNUP 100% ────────────────────────
│
▼ (friction: forms, email verify)
GET API KEY 70% ─────────────────
│
▼ (friction: complexity, confusion)
FIRST API CALL 40% ───────────
│
▼ (friction: setup, environment)
USE CASE COMPLETED 20% ─────
│
▼ (friction: edge cases, support)
ACTIVE DEVELOPER 10% ──
Goal: Maximize conversion at each step.
Time-to-First-Call Benchmarks
| Rating | Time | Example Actions |
|---|---|---|
| Excellent | < 5 minutes | Stripe, Twilio |
| Good | 5-15 minutes | Most modern APIs |
| Acceptable | 15-30 minutes | Enterprise APIs |
| Poor | 30-60 minutes | Needs improvement |
| Failing | > 60 minutes | Major DX investment needed |
Onboarding Flow Design
Good onboarding flow:
1. SIGNUP (30 seconds)
- OAuth/SSO option
- Minimal fields (email + password)
- No email verification for sandbox
2. API KEY (1 minute)
- Auto-generated on signup
- Visible immediately on dashboard
- Copy button with feedback
3. QUICKSTART (5 minutes)
- Language selector
- Copy-paste code samples
- Test endpoint (sandbox)
4. FIRST SUCCESS (10 minutes)
- Guided tutorial
- Real use case completed
- Clear next steps
Bad onboarding flow:
1. Long signup form (5+ fields)
2. Email verification required
3. Company approval/waitlist
4. Dashboard tour that can't be skipped
5. API key hidden in settings
6. No code samples
7. Documentation only (no quickstart)
Quickstart Page Anatomy
Essential elements:
# Quickstart
## 1. Install the SDK
[Language tabs: curl | Python | Node | Ruby | Go | Java]
```bash
pip install example-sdk
2. Get Your API Key
Your API key: sk_test_4eC39Hq... [Copy]
3. Make Your First Request
import example
client = example.Client("sk_test_4eC39HqL...")
result = client.users.create(email="[email protected]")
print(result.id) # usr_123abc
4. Verify It Worked
Check your [Dashboard →] to see the user you just created.
Next Steps
- [Complete guide →]
- [API reference →]
- [Code examples →]
### Interactive Sandbox Requirements
| Feature | Priority | Impact |
|---------|----------|--------|
| **No signup required** | Critical | Removes biggest friction |
| **Pre-filled examples** | Critical | Instant start |
| **Real API responses** | High | Builds confidence |
| **Multiple languages** | High | Serves all developers |
| **Save/share code** | Medium | Team collaboration |
| **Error explanations** | Medium | Self-service debugging |
### Onboarding Checklist Pattern
**Dashboard progress indicator:**
Getting Started 2 of 4 complete ─────────────────────────────────────────────────
[✓] Create account [✓] Get API key [ ] Make first API call [Start →] [ ] Set up webhooks [Learn more →]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
### First-Run Experience Best Practices
**Do:**
- Show API key immediately after signup
- Provide one-click copy for keys and code
- Default to test/sandbox mode
- Include inline code validation
- Show success confirmation
- Suggest logical next steps
**Don't:**
- Require payment info for trial
- Hide API keys in nested menus
- Force dashboard tour
- Start in production mode
- Leave developers wondering "did it work?"
### Language Prioritization
**Prioritize SDK/docs by adoption:**
| Priority | Languages | Reasoning |
|----------|-----------|-----------|
| **Tier 1** | Python, JavaScript/Node, curl | Highest adoption |
| **Tier 2** | Go, Ruby, Java | Strong in specific verticals |
| **Tier 3** | PHP, C#/.NET | Enterprise/legacy |
| **Tier 4** | Swift, Kotlin | Mobile-specific |
### Measuring Onboarding Success
**Key metrics:**
| Metric | Formula | Target |
|--------|---------|--------|
| **Time to API key** | Signup → key generated | < 2 min |
| **Time to first call** | Signup → first API request | < 15 min |
| **Activation rate** | First call / Signups | > 50% |
| **Day 1 retention** | Active day 2 / Signups | > 30% |
| **Time to value** | Signup → use case complete | < 1 hour |
### Onboarding Error Handling
**Good error experience:**
```json
{
"error": {
"code": "invalid_api_key",
"message": "The API key provided is invalid",
"suggestion": "Make sure you're using your API key from the dashboard, not the publishable key",
"doc_url": "https://docs.example.com/authentication"
}
}
Inline suggestions:
Error: 401 Unauthorized
Looks like your API key might be incorrect. Here's how to fix it:
1. Go to Dashboard → API Keys
2. Copy your Secret Key (starts with sk_)
3. Make sure you're using the test key for sandbox
[Get your API key →]
Anti-Patterns
- Sales wall — Requiring demo/call before API access
- Key scavenger hunt — API key buried in settings
- Docs-only quickstart — No interactive elements
- Production-first — Starting developers in production mode
- No curl examples — Forcing SDK for simple tests
- One-size-fits-all — Same onboarding for all use cases
- Abandoned onboarding — No follow-up for stuck developers
- Success silence — No confirmation when things work
title: Developer-Friendly Error Handling impact: HIGH tags: dx, errors, debugging, developer-experience
Developer-Friendly Error Handling
Impact: HIGH
Errors are inevitable. Great error handling turns frustration into quick fixes. Bad error handling drives developers to competitors.
Error Response Anatomy
Complete error response:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "The amount must be a positive integer representing cents",
"param": "amount",
"doc_url": "https://docs.example.com/api/errors#parameter_invalid",
"request_id": "req_7d82fcb9e3a4"
}
}
Essential error fields:
| Field | Purpose | Required |
|---|---|---|
| type | Error category | Yes |
| code | Specific error code | Yes |
| message | Human-readable explanation | Yes |
| param | Which parameter caused error | When applicable |
| doc_url | Link to documentation | Recommended |
| request_id | For support/debugging | Yes |
HTTP Status Code Usage
| Code | Name | When to Use |
|---|---|---|
| 400 | Bad Request | Invalid syntax, malformed request |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Valid auth but insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource state conflict (duplicate, etc.) |
| 422 | Unprocessable Entity | Validation failed on valid syntax |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side failure |
| 502 | Bad Gateway | Upstream service failure |
| 503 | Service Unavailable | Maintenance or overload |
Error Type Categories
error.type
├── api_error # Server-side issues
│ ├── internal_error
│ ├── service_unavailable
│ └── upstream_error
│
├── authentication_error # Auth issues
│ ├── invalid_api_key
│ ├── expired_token
│ └── missing_auth
│
├── authorization_error # Permission issues
│ ├── insufficient_permissions
│ └── restricted_resource
│
├── invalid_request_error # Client mistakes
│ ├── parameter_missing
│ ├── parameter_invalid
│ └── parameter_unknown
│
├── rate_limit_error # Rate limiting
│ └── too_many_requests
│
└── resource_error # Resource issues
├── resource_not_found
├── resource_already_exists
└── resource_locked
Validation Error Details
Good validation error (multiple fields):
{
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "The request contains invalid parameters",
"details": [
{
"field": "email",
"code": "invalid_format",
"message": "Email must be a valid email address"
},
{
"field": "amount",
"code": "out_of_range",
"message": "Amount must be between 100 and 10000000 (in cents)"
},
{
"field": "metadata.key",
"code": "invalid_characters",
"message": "Metadata keys can only contain letters, numbers, and underscores"
}
],
"request_id": "req_abc123"
}
}
Error Message Writing Guide
Good error messages:
| Bad | Good |
|---|---|
| "Invalid input" | "The email address format is invalid. Expected: [email protected]" |
| "Error 1234" | "Your API key has expired. Generate a new key at dashboard.example.com/keys" |
| "Something went wrong" | "The payment processor is temporarily unavailable. Retry in 30 seconds." |
| "null" | "The 'customer_id' field is required" |
| "Unauthorized" | "The API key provided does not have permission to access this resource" |
Message components:
- What happened — Clear description of the problem
- Why it happened — Context if helpful
- How to fix it — Actionable next steps
Idempotent Error Handling
Same request should return same error:
Request 1:
POST /v1/charges
{ "amount": -100 }
Response 1:
HTTP 422
{ "error": { "code": "invalid_amount", "message": "Amount must be positive" } }
Request 2 (identical):
POST /v1/charges
{ "amount": -100 }
Response 2 (identical):
HTTP 422
{ "error": { "code": "invalid_amount", "message": "Amount must be positive" } }
Retry-After Headers
For rate limits and temporary errors:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
{
"error": {
"type": "rate_limit_error",
"code": "too_many_requests",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"retry_after": 60
}
}
For maintenance:
HTTP/1.1 503 Service Unavailable
Retry-After: 300
{
"error": {
"type": "api_error",
"code": "service_unavailable",
"message": "The API is undergoing maintenance. Expected back at 2024-01-15T10:00:00Z",
"maintenance_expected_end": "2024-01-15T10:00:00Z"
}
}
SDK Error Handling
Good SDK error design:
from example import Client
from example.errors import (
AuthenticationError,
RateLimitError,
ValidationError,
APIError
)
client = Client("sk_test_...")
try:
charge = client.charges.create(amount=1000)
except AuthenticationError as e:
# API key issue
print(f"Check your API key: {e.message}")
except RateLimitError as e:
# Wait and retry
time.sleep(e.retry_after)
retry()
except ValidationError as e:
# Fix the request
for detail in e.details:
print(f"{detail.field}: {detail.message}")
except APIError as e:
# Server error - log and alert
print(f"API error: {e.message}")
print(f"Request ID: {e.request_id}")
alert_team(e)
Error Logging for Debugging
What to include in error logs:
{
"timestamp": "2024-01-15T10:30:00Z",
"request_id": "req_abc123",
"error_code": "validation_failed",
"path": "/v1/charges",
"method": "POST",
"client_ip": "203.0.113.50",
"api_version": "2024-01-01",
"params_sanitized": {
"amount": 1000,
"currency": "usd"
},
"error_details": [...],
"stack_trace": "..." // Internal only
}
Deprecation Warnings
Include warnings for deprecated features:
{
"data": { ... },
"warnings": [
{
"code": "deprecated_parameter",
"message": "The 'source' parameter is deprecated. Use 'payment_method' instead.",
"deprecated_at": "2024-01-01",
"removal_date": "2024-07-01",
"doc_url": "https://docs.example.com/migration/payment-methods"
}
]
}
Error Documentation
Document every error code:
# API Errors Reference
## authentication_error
### invalid_api_key
Your API key is invalid or has been revoked.
**Causes:**
- Typo in API key
- Using test key in production (or vice versa)
- Key has been deleted
**Solutions:**
1. Verify your key at Dashboard → API Keys
2. Ensure you're using the correct environment key
3. Generate a new key if needed
**Example:**
```json
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "The API key provided is invalid"
}
}
### Anti-Patterns
- **Generic errors** — "Something went wrong" tells developers nothing
- **Missing request IDs** — Can't trace errors through support
- **Wrong status codes** — Using 200 for errors, 500 for validation
- **No error codes** — Only human messages, can't programmatically handle
- **Exposing internals** — Stack traces, database errors in production
- **Inconsistent format** — Different error shapes for different endpoints
- **No retry guidance** — Rate limits without Retry-After
- **Undocumented errors** — Error codes not in documentation
- **Leaking information** — "User not found" vs "Invalid credentials"
- **Silent failures** — Succeeding when it should error
title: Integration Marketplace Strategy impact: MEDIUM tags: marketplace, integrations, partners, ecosystem
Integration Marketplace Strategy
Impact: MEDIUM
An integration marketplace multiplies your platform's value. Every integration makes your platform stickier and more valuable.
Marketplace Maturity Stages
| Stage | Integrations | Focus | Strategy |
|---|---|---|---|
| Bootstrap | 0-10 | Core integrations | Build in-house |
| Foundation | 10-50 | Key categories | Partner outreach |
| Growth | 50-200 | Long tail | Self-serve partner portal |
| Scale | 200+ | Ecosystem flywheel | Revenue sharing, acquisition |
Integration Types
| Type | Who Builds | Maintenance | Example |
|---|---|---|---|
| Native | Your team | You | Core CRM sync |
| Partner-built | Partners | Partner | Salesforce connector |
| Community | Developers | Community | Open source tools |
| Embedded | Your platform | You | White-label options |
Integration Prioritization Framework
Score each integration (1-5):
| Factor | Weight | Questions |
|---|---|---|
| Customer demand | 30% | How many customers request this? |
| Retention impact | 25% | Will this reduce churn? |
| Acquisition impact | 20% | Will this drive new signups? |
| Strategic fit | 15% | Does this support our positioning? |
| Build effort | 10% | How hard is this to build? |
Integration Priority Matrix:
HIGH DEMAND
│
┌─────────────┼─────────────┐
│ PARTNER │ BUILD │
│ FIRST │ NOW │
│ │ │
LOW EFFORT─┼─────────────┼─────────────┼─HIGH EFFORT
│ │ │
│ DEPRIORITIZE PARTNER │
│ │ ONLY │
└─────────────┼─────────────┘
│
LOW DEMAND
Partner Portal Requirements
Self-serve partner program needs:
| Feature | Priority | Purpose |
|---|---|---|
| Registration | Critical | Onboard partners |
| API documentation | Critical | Build integrations |
| Sandbox environment | Critical | Test without risk |
| App submission | Critical | List integrations |
| Review dashboard | High | Track status |
| Analytics | High | Usage metrics |
| Revenue reports | Medium | Payouts, tracking |
| Co-marketing tools | Medium | Launch support |
Integration Listing Page Anatomy
Good marketplace listing:
# Slack Integration
Connect your Example account to Slack for real-time notifications.
## Features
- Get notified when orders are placed
- Receive alerts for failed payments
- Daily summary reports to channel
## Setup (2 minutes)
1. Click "Connect Slack"
2. Choose your workspace
3. Select notification channel
4. Configure alert preferences
## Pricing
Free with all plans
## Requirements
- Slack workspace admin access
- Example account (any plan)
[Install Integration]
---
## Reviews (4.8 ★ from 234 reviews)
"This saves our team hours every week..." - Sarah, Acme Corp
## Support
Built by Example • [email protected]
Revenue Sharing Models
| Model | Structure | Best For |
|---|---|---|
| Free listing | No fees | Early marketplace |
| Revenue share | 15-30% of integration revenue | Mature marketplace |
| Flat fee | $X/month listing | High-value integrations |
| Tiered | Better terms at scale | Growing partners |
| Referral bonus | % of referred revenue | Acquisition-focused |
Example revenue share tiers:
| Partner Revenue | Your Take Rate |
|---|---|
| $0 - $10K/mo | 30% |
| $10K - $50K/mo | 25% |
| $50K - $100K/mo | 20% |
| $100K+/mo | 15% |
Integration Quality Standards
Certification requirements:
| Requirement | Reason |
|---|---|
| Working demo | Proves functionality |
| Error handling | Reliable user experience |
| Documentation | User can self-serve |
| Support contact | Issues can be resolved |
| Security review | Protect user data |
| Performance test | Won't degrade platform |
| OAuth compliance | Proper authentication |
Partner Success Metrics
| Metric | Definition | Target |
|---|---|---|
| Install rate | Installs / Listing views | > 10% |
| Activation rate | Active / Installed | > 50% |
| Retention | Still active at 90 days | > 70% |
| NPS | Partner satisfaction | > 40 |
| Support ratio | Tickets / Active installs | < 5% |
Go-to-Market for Integrations
Launch checklist:
□ Integration working in production
□ Listing page complete
□ Documentation published
□ Support team briefed
□ Blog post drafted
□ Email to existing users
□ Social media posts scheduled
□ Partner co-marketing aligned
□ Sales team enablement
□ Success metrics defined
Marketplace Categories
Organize integrations by:
| Category Type | Examples |
|---|---|
| Use case | Marketing, Sales, Support |
| Tool type | CRM, Analytics, Communication |
| Industry | Healthcare, Finance, Retail |
| Functionality | Import, Export, Sync, Automate |
Webhook Marketplace Pattern
For event-based integrations:
Your Platform → Webhook Events → Partner Apps
Events available:
- order.created
- order.updated
- customer.created
- payment.completed
- subscription.cancelled
Partner registers endpoint:
POST https://partner.com/webhook
Headers: X-Signature: sha256=...
Body: { event, data, timestamp }
Build vs Partner vs Buy
| Factor | Build | Partner | Buy/Acquire |
|---|---|---|---|
| Control | Full | Limited | Full |
| Speed | Slow | Medium | Fast |
| Cost | High | Low | Very High |
| Quality | High | Variable | High |
| Maintenance | You | Partner | You |
Decision framework:
- Build when: Core to value prop, strategic, high quality bar
- Partner when: Not core, partner has expertise, speed matters
- Buy when: Critical gap, strong partner exists, can afford
Anti-Patterns
- Vanity integrations — Building for logos, not users
- No quality bar — Approving broken integrations
- Zombie marketplace — Integrations nobody uses
- Partner neglect — No support for integration builders
- Revenue obsession — High take rates killing ecosystem
- Certification theater — Badges without real review
- Category sprawl — Too many categories, hard to navigate
- No removal process — Can't sunset bad integrations
- Competitive blindness — Not integrating with competitors
title: Platform Metrics and Health impact: CRITICAL tags: metrics, analytics, health, monitoring, kpis
Platform Metrics and Health
Impact: CRITICAL
What you measure defines what you improve. Platform health metrics span technical reliability, developer experience, and business outcomes.
The Platform Metrics Hierarchy
┌─────────────────────────────────────────────────────────────┐
│ BUSINESS METRICS │
│ Revenue • NDR • Customer Count • Expansion Revenue │
├─────────────────────────────────────────────────────────────┤
│ DEVELOPER METRICS │
│ MAD • API Calls • Retention • Use Cases Completed │
├─────────────────────────────────────────────────────────────┤
│ EXPERIENCE METRICS │
│ TTFC • Activation • Support Tickets • CSAT │
├─────────────────────────────────────────────────────────────┤
│ PLATFORM METRICS │
│ Uptime • Latency • Error Rate • Throughput │
└─────────────────────────────────────────────────────────────┘
Core Platform Health Metrics
| Metric | Definition | Target | Alert Threshold |
|---|---|---|---|
| Uptime | Available time / Total time | 99.9%+ | < 99.5% |
| P50 Latency | Median response time | < 100ms | > 200ms |
| P99 Latency | 99th percentile response | < 500ms | > 1000ms |
| Error Rate | Errors / Total requests | < 0.1% | > 1% |
| Throughput | Requests per second | Baseline | 50% drop |
Developer Experience Metrics
| Metric | Formula | Target | How to Measure |
|---|---|---|---|
| Time to First Call (TTFC) | Signup → First API call | < 15 min | Event tracking |
| Activation Rate | Made API call / Signups | > 50% | Funnel analysis |
| Day 7 Retention | Active day 7 / Signups | > 25% | Cohort analysis |
| Day 30 Retention | Active day 30 / Signups | > 15% | Cohort analysis |
| Support Ticket Rate | Tickets / Active developers | < 5% | Support data |
Business Metrics for Platforms
| Metric | Definition | Target | Calculation |
|---|---|---|---|
| MAD | Monthly Active Developers | Growth | Unique developers with API calls |
| API Call Volume | Total monthly API calls | Growth | Sum all requests |
| Revenue per Developer | MRR / MAD | Stable/growth | Financial / usage data |
| Net Dollar Retention | Expansion - Churn | > 110% | Revenue tracking |
| Logo Retention | Customers retained | > 90% | Customer count |
Developer Funnel Metrics
Stage Metric Target
─────────────────────────────────────────────────────
AWARENESS Site visits Growth
Docs visits Growth
│
▼
SIGNUP Signups Growth
Signup conversion > 5%
│
▼
ACTIVATION API key generated > 80% of signups
First API call > 50% of signups
│
▼
ENGAGEMENT Weekly active > 30% of activated
API calls/developer Growing
│
▼
EXPANSION Upgrade rate > 10%
API volume growth > 20% MoM
│
▼
ADVOCACY Referrals Growing
Community contributions Growing
SLA Definitions
Standard SLA tiers:
| Tier | Uptime | Support Response | Use Case |
|---|---|---|---|
| Basic | 99.5% | 24h | Free, starter |
| Pro | 99.9% | 4h business hours | Growth |
| Enterprise | 99.99% | 1h, 24/7 | Critical infrastructure |
SLA calculation:
Monthly Uptime = (Total Minutes - Downtime Minutes) / Total Minutes
43,200 minutes in a month (30 days)
99.9% = max 43 minutes downtime
99.95% = max 22 minutes downtime
99.99% = max 4 minutes downtime
Error Rate Analysis
Track errors by category:
| Category | Example Codes | Owner | Action |
|---|---|---|---|
| Client errors | 400, 401, 403, 404, 422 | Developer | Better docs, validation |
| Server errors | 500, 502, 503 | Engineering | Fix bugs, scale |
| Rate limits | 429 | Product | Adjust limits, offer upgrades |
| Timeouts | 504 | Engineering | Optimize, scale |
Error budget:
Error Budget = 1 - SLA
For 99.9% SLA:
Error budget = 0.1% of requests can fail
Or ~43 minutes downtime/month
API Usage Dashboards
Essential dashboard views:
| Dashboard | Metrics | Audience |
|---|---|---|
| Real-time | Requests/sec, errors, latency | On-call engineering |
| Daily health | Uptime, P99, error rate | Engineering leads |
| Developer activity | TTFC, activation, retention | Product |
| Business | MAD, revenue, growth | Leadership |
| Customer-facing | Status page | All users |
Cohort Analysis for Developers
Track developer cohorts over time:
Week 0 Week 1 Week 2 Week 4 Week 8
Jan '24 100% 45% 32% 25% 20%
Feb '24 100% 52% 38% 30% 24%
Mar '24 100% 58% 45% 35% 28%
↑
Week-over-week improvement shows
onboarding changes working
Rate Limiting Metrics
| Metric | Purpose | Target |
|---|---|---|
| Rate limit hits | Capacity planning | < 1% of requests |
| Developers hitting limits | Pricing/limits fit | < 5% |
| Limit → upgrade rate | Monetization | > 10% |
| Limit → churn rate | Friction indicator | < 5% |
Developer Satisfaction Metrics
| Metric | Collection Method | Frequency | Target |
|---|---|---|---|
| NPS | In-product survey | Quarterly | > 50 |
| CSAT | Post-support survey | Per ticket | > 4.5/5 |
| CES | Task completion survey | Per flow | < 2 (low effort) |
| Developer feedback | Community, support | Continuous | Qualitative |
Alerting Strategy
Alert priority levels:
| Level | Response Time | Example | Notification |
|---|---|---|---|
| P1 - Critical | < 15 min | Full outage | Page, all channels |
| P2 - High | < 1 hour | Partial outage | Page on-call |
| P3 - Medium | < 4 hours | Degraded performance | Slack alert |
| P4 - Low | Next business day | Anomaly detected | Email digest |
Metric Review Cadence
| Cadence | Metrics | Attendees |
|---|---|---|
| Real-time | Uptime, errors, latency | On-call |
| Daily | API health, support queue | Eng leads |
| Weekly | Developer funnel, activation | Product team |
| Monthly | MAD, retention, business | Leadership |
| Quarterly | NPS, satisfaction, strategy | Exec + Product |
Anti-Patterns
- Vanity metrics — Tracking total users not active users
- No segmentation — Same metrics for all developer types
- Lagging only — No leading indicators
- Alert fatigue — Too many non-actionable alerts
- No benchmarks — Metrics without targets
- Metric silos — Platform and business metrics disconnected
- Gaming metrics — Optimizing metric not outcome
- Invisible failures — Not tracking silent errors
- Missing cohorts — Aggregate only, no time-based analysis
title: SDK and Library Strategy impact: HIGH tags: sdk, libraries, developer-tools, client-libraries
SDK and Library Strategy
Impact: HIGH
SDKs are how most developers experience your API. They should feel native to each language ecosystem.
SDK Value Proposition
Why build SDKs:
| Benefit | Without SDK | With SDK |
|---|---|---|
| Time to integrate | Hours | Minutes |
| Error handling | Manual parsing | Built-in |
| Auth management | DIY token refresh | Automatic |
| Type safety | None | Full types |
| Updates | Manual changes | Package update |
| Best practices | Hope they know | Enforced |
Language Prioritization Framework
Tier 1 - Essential (build first):
| Language | Why | Ecosystem |
|---|---|---|
| Python | Data, ML, scripting | pip |
| JavaScript/Node | Web, serverless | npm |
| curl/HTTP | Universal testing | N/A |
Tier 2 - Growth (build second):
| Language | Why | Ecosystem |
|---|---|---|
| Go | Cloud-native, infra | go modules |
| Ruby | Web apps, Rails | gem |
| Java | Enterprise | Maven/Gradle |
Tier 3 - Expand (based on demand):
| Language | Why | Ecosystem |
|---|---|---|
| PHP | WordPress, legacy web | Composer |
| C#/.NET | Enterprise, Microsoft | NuGet |
| Swift | iOS native | Swift Package Manager |
| Kotlin | Android native | Maven/Gradle |
| Rust | Systems, performance | Cargo |
SDK Generation Strategies
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Hand-written | Native feel, optimized DX | Expensive to maintain | Core SDKs, Tier 1 languages |
| Code generation | Consistent, easy to update | Can feel generic | Many languages, rapid iteration |
| OpenAPI generators | Industry standard | Limited customization | Getting started |
| Hybrid | Best of both | Complexity | Scale with quality |
Recommended approach:
- Tier 1: Hand-written for best DX
- Tier 2: Generated with hand-written improvements
- Tier 3: Generated from OpenAPI spec
SDK Design Principles
1. Follow language conventions:
# Python: snake_case, context managers
with client.batch() as batch:
batch.create_user(email="[email protected]")
batch.create_order(user_id="usr_123")
// JavaScript: camelCase, promises/async
const user = await client.users.create({
email: '[email protected]'
});
// Go: exported types, explicit errors
user, err := client.Users.Create(ctx, &CreateUserParams{
Email: "[email protected]",
})
2. Consistent resource access pattern:
# Resource-based access
client.users.create(...)
client.users.retrieve("usr_123")
client.users.update("usr_123", ...)
client.users.delete("usr_123")
client.users.list(limit=10)
# Nested resources
client.users.orders.list("usr_123")
3. Type safety where available:
// TypeScript: Full type definitions
interface CreateUserParams {
email: string;
name?: string;
metadata?: Record<string, string>;
}
interface User {
id: string;
email: string;
name: string | null;
created_at: string;
}
const user: User = await client.users.create({
email: '[email protected]'
});
SDK Feature Checklist
| Feature | Priority | Notes |
|---|---|---|
| Authentication | Critical | API key, OAuth support |
| Automatic retries | Critical | Exponential backoff |
| Error handling | Critical | Typed exceptions |
| Request/response logging | High | Debug mode |
| Pagination helpers | High | Iterate without manual paging |
| Idempotency support | High | Built-in key generation |
| Webhook verification | High | Signature validation |
| Type definitions | High | TypeScript, Python types |
| Timeout configuration | Medium | Request-level timeouts |
| Proxy support | Medium | Enterprise requirements |
| Custom HTTP client | Medium | Testing, customization |
Error Handling Pattern
Good SDK error design:
from example import Client, APIError, AuthenticationError, RateLimitError
client = Client("sk_test_...")
try:
user = client.users.create(email="invalid")
except AuthenticationError as e:
# Invalid API key
print(f"Auth failed: {e.message}")
except RateLimitError as e:
# Rate limited - retry after delay
print(f"Rate limited. Retry after {e.retry_after}s")
except APIError as e:
# General API error
print(f"API error: {e.code} - {e.message}")
print(f"Request ID: {e.request_id}")
Error class hierarchy:
ExampleError (base)
├── APIError
│ ├── AuthenticationError
│ ├── AuthorizationError
│ ├── NotFoundError
│ ├── ValidationError
│ └── RateLimitError
├── NetworkError
│ ├── TimeoutError
│ └── ConnectionError
└── SDKError
└── ConfigurationError
Pagination Patterns
Good pagination (auto-pagination):
# Iterate all users without manual paging
for user in client.users.list(limit=100):
process(user)
# Or with async
async for user in client.users.list():
await process(user)
Manual pagination access:
page = client.users.list(limit=10)
print(page.data) # List of users
print(page.has_more) # Boolean
print(page.next_page()) # Get next page
SDK Documentation Requirements
Per-language docs should include:
- Installation instructions
- Authentication setup
- Basic usage example
- Error handling
- Advanced configuration
- Migration guides (version upgrades)
Example structure:
# Python SDK
## Installation
pip install example-sdk
## Quick Start
```python
from example import Client
client = Client("sk_test_...")
Authentication
[Details on API keys, OAuth, etc.]
Resources
Error Handling
[Exception types and handling]
Configuration
[Timeouts, retries, logging]
### SDK Versioning Strategy
| API Change | SDK Change | Semver |
|------------|------------|--------|
| New endpoint | New method | Minor (1.x.0) |
| New optional param | New optional param | Minor (1.x.0) |
| New required param | Breaking change | Major (x.0.0) |
| Endpoint removed | Method removed | Major (x.0.0) |
| Bug fix | Bug fix | Patch (1.0.x) |
**Version support policy:**
Current major version: Full support Previous major version: Security fixes only (12 months) Older versions: Unsupported
### Testing Strategy for SDKs
| Test Type | Purpose | Coverage |
|-----------|---------|----------|
| **Unit tests** | SDK logic | 90%+ |
| **Integration tests** | Real API calls | Key flows |
| **Generated tests** | OpenAPI compliance | All endpoints |
| **Example tests** | Docs accuracy | All examples |
### Anti-Patterns
- **Thin wrappers** — SDK that just wraps HTTP with no added value
- **Non-idiomatic code** — Ruby SDK that feels like Java
- **Missing types** — No TypeScript definitions for JS
- **Inconsistent naming** — Different patterns across languages
- **No error context** — Generic errors without details
- **Version lock** — SDK pinned to specific API version
- **Giant SDK** — Including everything vs. modular packages
- **No changelog** — Updates without migration guidance
title: API Versioning and Deprecation impact: HIGH tags: versioning, deprecation, breaking-changes, migration
API Versioning and Deprecation
Impact: HIGH
How you version and deprecate defines developer trust. Breaking changes without warning destroys relationships.
Versioning Strategies Comparison
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | /v1/users | Clear, cacheable | URL changes |
| Header | API-Version: 2024-01 | Clean URLs | Hidden version |
| Query param | /users?version=1 | Easy to test | Not RESTful |
| Date-based | Stripe-Version: 2024-01-15 | Granular | Many versions |
| No versioning | Additive only | Simple | Limits evolution |
Recommended: URL path versioning for major versions + date-based for minor
Version Lifecycle Stages
┌─────────┐ ┌──────────┐ ┌────────────┐ ┌───────────┐
│ ALPHA │ → │ BETA │ → │ STABLE │ → │DEPRECATED │ → SUNSET
│ │ │ │ │ │ │ │
│No SLA │ │Limited │ │Full SLA │ │Migration │
│May break│ │SLA │ │Support │ │period │
└─────────┘ └──────────┘ └────────────┘ └───────────┘
Breaking vs Non-Breaking Changes
Non-breaking (safe to ship anytime):
| Change Type | Example |
|---|---|
| Add endpoint | New POST /v1/refunds |
| Add optional param | New metadata field |
| Add response field | New updated_at field |
| Expand enum values | New status pending_review |
| Increase limit | Rate limit 100→200/min |
| Add webhook event | New invoice.finalized |
Breaking (requires new version):
| Change Type | Example |
|---|---|
| Remove endpoint | Delete GET /v1/legacy |
| Remove field | Remove card_number |
| Change field type | amount: string → integer |
| Add required param | New required currency |
| Remove enum value | Remove cancelled status |
| Change URL structure | /users/{id} → /customers/{id} |
| Change authentication | API key → OAuth only |
Deprecation Timeline
Recommended minimum timeline:
| Stage | Duration | Actions |
|---|---|---|
| Announcement | Day 0 | Blog post, changelog, email |
| Soft deprecation | 6 months | Warnings in responses |
| Hard deprecation | 12 months | Errors for new integrations |
| Sunset | 18-24 months | API removed |
Communication cadence:
Month 0: Deprecation announced
Month 3: Reminder email
Month 6: Warning headers start
Month 9: Final reminder
Month 12: New integrations blocked
Month 18: Final sunset warning
Month 24: API sunset
Deprecation Header Pattern
Response headers for deprecated endpoints:
HTTP/1.1 200 OK
Deprecation: Sun, 01 Jan 2025 00:00:00 GMT
Sunset: Sun, 01 Jul 2025 00:00:00 GMT
Link: <https://docs.example.com/migration>; rel="deprecation"
Warning in response body:
{
"data": { ... },
"warnings": [
{
"code": "deprecated_endpoint",
"message": "This endpoint is deprecated and will be removed on 2025-07-01",
"doc_url": "https://docs.example.com/v2-migration"
}
]
}
Migration Guide Structure
Every breaking change needs a migration guide:
# Migrating from v1 to v2
## Overview
v2 introduces improved error handling and consistent naming.
## Timeline
- v2 released: January 1, 2024
- v1 deprecated: July 1, 2024
- v1 sunset: January 1, 2025
## Breaking Changes
### 1. Customer endpoint renamed
**Before (v1):**
```bash
GET /v1/users/{id}
After (v2):
GET /v2/customers/{id}
Migration steps:
- Update endpoint URLs in your code
- Update SDK to v2.0
- Update field mappings (see below)
2. Response format changes
Before (v1):
{"user_id": "123", "mail": "[email protected]"}
After (v2):
{"id": "cus_123", "email": "[email protected]"}
SDK Upgrade Guide
[Link to SDK migration docs]
Need Help?
Contact [email protected] for migration assistance.
### Version Coexistence Strategy
**Running multiple versions:**
/v1/* → v1 handlers (maintenance mode) /v2/* → v2 handlers (active development)
Shared: Auth, rate limiting, logging Separate: Business logic, response formatting
**Feature flagging approach:**
```python
def get_user(user_id, version):
user = db.get_user(user_id)
if version == "v1":
return format_v1_response(user)
elif version == "v2":
return format_v2_response(user)
Changelog Best Practices
Good changelog entry:
## 2024-01-15
### Added
- New `refunds` endpoint for processing refunds
- `metadata` field on all resources
### Changed
- Rate limits increased from 100 to 200 requests/minute
- `status` field now includes `pending_review` value
### Deprecated
- `GET /v1/users` - Use `GET /v2/customers` instead
Sunset date: 2025-01-15
[Migration guide →]
### Fixed
- Fixed pagination returning duplicate results
Version Support Matrix
Communicate clearly what's supported:
| Version | Status | Support Level | End of Life |
|---|---|---|---|
| v3 | Current | Full support | TBD |
| v2 | Maintained | Security only | 2025-06-01 |
| v1 | Deprecated | None | 2024-12-31 |
Handling Breaking Changes Gracefully
Additive approach (preferred):
// Instead of changing existing fields...
{
"status": "completed" // Don't change this
}
// ...add new fields
{
"status": "completed",
"status_v2": {
"code": "completed",
"reason": "delivered"
}
}
Parallel endpoints during transition:
GET /v1/users/{id} # Old format, deprecated
GET /v2/customers/{id} # New format, recommended
Anti-Patterns
- Silent breaking changes — Changing behavior without versioning
- Perpetual beta — Using "beta" to avoid versioning commitment
- Too many versions — More than 2-3 active versions
- No sunset dates — Deprecated forever
- Short deprecation cycles — Less than 12 months
- No migration path — New version without upgrade guide
- Breaking date-based versions — Changing
2024-01-15after release - Version in body — Putting version in request body, not URL/header
- Forced upgrades — Sunsetting without adequate notice