API Products
Pay-per-call and usage-based API products
API Products
API Products are programmatic interfaces sold on a usage basis. Unlike SaaS products with UIs for humans, API products are code-first interfaces designed for developers to integrate into their applications. Pricing is typically pay-per-call or metered usage.
Characteristics
| Aspect | Value |
|---|---|
| Revenue Model | Usage-based (pay-per-call, pay-per-request) |
| Pricing | Per API call, per GB transferred, tiered volume |
| Primary Interface | REST API, GraphQL, tRPC |
| Target User | Developers (programmatic integration) |
| Developer Experience | Docs, playground, SDKs, code examples |
| Example Products | Stripe API, OpenAI API, Twilio API |
MDXLD Definition
---
$context: https://schema.org.ai
$type: Product
$id: https://acme.com/products/api
name: Acme Enrichment API
type: API
business: https://acme.com
description: RESTful API for data enrichment and validation
isActive: true
# Categorization
industries:
- B2B SaaS
- Data & Analytics
productCategories:
- API Platform
- Data Enrichment
# Interfaces
interfaces:
hasAPI: true
hasRPC: false
hasMCP: true
hasSDK: true
hasCLI: true
# Usage-based pricing
prices:
- $id: price_free
name: Free Tier
amount: 0
currency: USD
type: usage
interval: request
includedQuantity: 1000
description: 1,000 requests/month free
- $id: price_basic
name: Pay as You Go
amount: 0.001
currency: USD
type: usage
interval: request
description: $1 per 1,000 requests
- $id: price_volume
name: Volume Pricing
amount: 0.0005
currency: USD
type: usage
interval: request
minVolume: 1000000
description: $0.50 per 1,000 requests (1M+ volume)
---
# {name}
{description}
<Hero title={name} subtitle={description} cta="View API Docs" />
## Features
<Features product={$id} />
## API Reference
<APIReference product={$id} />
## Pricing
<Pricing prices={prices} />
## Code Examples
<CodeExamples product={$id} />Component Structure
API products render as developer-focused sites and tools:
APISite (Marketing + Docs)
// Marketing site with integrated documentation
<APISite product={apiProduct}>
<Header />
<LandingPage>
<Hero />
<CodeExample />
<Features />
<Pricing />
</LandingPage>
<APIReference />
<Playground />
<Footer />
</APISite>DeveloperApp (Dashboard)
// Developer dashboard for API management
<DeveloperApp product={apiProduct}>
<Shell>
<Sidebar />
<Dashboard>
<UsageMetrics />
<APIKeys />
<Webhooks />
<Logs />
<Billing />
</Dashboard>
</Shell>
</DeveloperApp>Pricing Models
Pay-Per-Call
Simple per-request pricing:
interface PayPerCall {
type: 'usage'
interval: 'request' | 'call'
amount: number // e.g., $0.001 per request
includedQuantity?: number // Free tier: 1,000 requests
description: string
}Example: $0.001 per API request ($1 per 1,000 requests)
Tiered Volume Pricing
Lower rates at higher volumes:
interface TieredVolumePricing {
type: 'usage'
tiers: Array<{
upTo: number // Requests per month
amount: number // Price per request
description: string
}>
}
const pricingTiers = [
{ upTo: 10000, amount: 0.01, description: "First 10K: $0.01/request" },
{ upTo: 100000, amount: 0.005, description: "10K-100K: $0.005/request" },
{ upTo: Infinity, amount: 0.001, description: "100K+: $0.001/request" },
]Example: First 10K @ $0.01, next 90K @ $0.005, 100K+ @ $0.001
Subscription + Usage
Base subscription with included quota:
interface SubscriptionPlusUsage {
baseSubscription: {
amount: number // $49/month
interval: 'month' | 'year'
includedQuota: number // 100,000 requests included
}
overageRate: number // $0.001 per additional request
}Example: $49/month including 100K requests, then $0.001 per additional request
Metered Resources
Charge for different resource types:
interface MeteredPricing {
metrics: Array<{
name: string // 'api_requests', 'data_transfer_gb', 'ai_tokens'
amount: number // Price per unit
unit: string // 'request', 'GB', 'token'
includedQuantity?: number // Free allowance
}>
}
const metrics = [
{ name: 'api_requests', amount: 0.001, unit: 'request' },
{ name: 'data_transfer', amount: 0.10, unit: 'GB' },
{ name: 'webhook_deliveries', amount: 0.0001, unit: 'delivery' },
]Developer Experience
API Documentation
Essential documentation for API products:
| Section | Content |
|---|---|
| Quickstart | Get started in 5 minutes |
| Authentication | API keys, OAuth, JWT |
| API Reference | All endpoints, parameters, responses |
| Code Examples | Request/response examples in multiple languages |
| SDKs | Client libraries (JS, Python, Go, etc.) |
| Rate Limits | Usage limits and quotas |
| Webhooks | Event notifications |
| Errors | Error codes and handling |
Interactive Playground
<APIPlayground product={apiProduct}>
<RequestBuilder />
<CodeGenerator languages={['curl', 'javascript', 'python']} />
<ResponseViewer />
<SavedRequests />
</APIPlayground>Code Examples
// Auto-generate code examples for all endpoints
<CodeExamples
endpoint="/v1/enrichment"
languages={['curl', 'javascript', 'python', 'go', 'ruby']}
/>Example output:
# cURL
curl https://api.acme.com/v1/enrichment \
-H "Authorization: Bearer sk_test_..." \
-d email="user@example.com"// JavaScript
const acme = require('acme-sdk');
const client = new acme.Client('sk_test_...');
const result = await client.enrichment.lookup({
email: 'user@example.com'
});# Python
import acme
client = acme.Client('sk_test_...')
result = client.enrichment.lookup(
email='user@example.com'
)Developer Dashboard
Essential features in the developer portal:
API Keys
interface APIKey {
id: string
name: string
key: string // sk_live_... or sk_test_...
type: 'test' | 'live'
createdAt: Date
lastUsed?: Date
rateLimit?: number // Requests per minute
}
<APIKeys product={apiProduct}>
<CreateAPIKey />
<KeyList />
<RevokeKey />
<RegenerateKey />
</APIKeys>Usage Metrics
interface UsageMetrics {
period: 'day' | 'week' | 'month'
metrics: {
totalRequests: number
successRate: number // % of 2xx responses
p50Latency: number // Median response time (ms)
p99Latency: number // 99th percentile latency
errorRate: number // % of 4xx/5xx responses
quotaUsed: number // % of quota consumed
}
}
<UsageMetrics product={apiProduct}>
<RequestsChart />
<LatencyChart />
<ErrorRateChart />
<QuotaGauge />
</UsageMetrics>Request Logs
interface RequestLog {
id: string
timestamp: Date
method: string // GET, POST, etc.
endpoint: string
statusCode: number
latency: number // milliseconds
ip?: string
userAgent?: string
}
<RequestLogs product={apiProduct}>
<LogsTable />
<FilterByStatus />
<FilterByEndpoint />
<ExportLogs />
</RequestLogs>Webhooks
interface Webhook {
id: string
url: string
events: string[] // ['enrichment.completed', 'payment.failed']
secret: string // For signature verification
isActive: boolean
}
<Webhooks product={apiProduct}>
<CreateWebhook />
<WebhookList />
<TestWebhook />
<ViewDeliveries />
</Webhooks>API Key Management
API keys are the primary authentication mechanism for API products. Proper key management is critical for security and user experience.
Key Types and Environments
interface APIKey {
id: string
name: string // User-friendly label
key: string // The actual key
prefix: 'sk_test_' | 'sk_live_' // Environment prefix
environment: 'test' | 'production'
createdAt: Date
lastUsed?: Date
expiresAt?: Date // Optional expiration
permissions?: string[] // Scoped permissions
rateLimit?: number // Key-specific rate limit
metadata?: Record<string, any> // Custom metadata
}Key Generation Best Practices
// Secure key generation
function generateAPIKey(environment: 'test' | 'live'): string {
const prefix = environment === 'test' ? 'sk_test_' : 'sk_live_'
const random = crypto.randomBytes(32).toString('base64url')
return `${prefix}${random}`
}
// Key format: sk_test_abcd1234efgh5678ijkl9012mnop3456Key Scoping and Permissions
Limit key capabilities to follow principle of least privilege:
interface ScopedAPIKey extends APIKey {
permissions: {
resources: string[] // ['products', 'webhooks']
actions: Array<'read' | 'write' | 'delete'>
restrictions?: {
ipWhitelist?: string[] // Allowed IP addresses
referrerWhitelist?: string[] // Allowed domains
rateLimit?: number // Requests per minute
}
}
}
// Example: Read-only key for analytics
const analyticsKey: ScopedAPIKey = {
id: 'key_123',
name: 'Analytics Dashboard',
key: 'sk_live_...',
permissions: {
resources: ['analytics', 'usage'],
actions: ['read'],
restrictions: {
ipWhitelist: ['192.168.1.100'],
rateLimit: 100
}
}
}Key Rotation
interface KeyRotation {
strategy: 'manual' | 'automatic'
maxAge?: number // Days before expiration warning
gracePeriod?: number // Days both keys work during transition
notificationDays?: number[] // [30, 7, 1] days before expiry
}
// Automatic rotation flow
async function rotateAPIKey(keyId: string): Promise<{
newKey: APIKey
oldKey: APIKey
expiresAt: Date
}> {
const oldKey = await getAPIKey(keyId)
const newKey = await createAPIKey({
name: `${oldKey.name} (rotated)`,
environment: oldKey.environment,
permissions: oldKey.permissions
})
// Old key expires after grace period
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
await updateAPIKey(oldKey.id, { expiresAt })
return { newKey, oldKey, expiresAt }
}Security Best Practices
| Practice | Implementation |
|---|---|
| Prefix Keys | Use sk_test_ for test, sk_live_ for production |
| Hash Storage | Never store plaintext keys, use bcrypt/argon2 |
| Show Once | Display full key only on creation |
| Expiration | Optional TTL for temporary access |
| Audit Logs | Track all key usage and changes |
| Revocation | Instant key disable capability |
| IP Whitelisting | Restrict keys to specific IPs/ranges |
Rate Limiting
Rate limiting protects API infrastructure and ensures fair usage across all customers.
Rate Limit Strategies
Fixed Window
Simple time-based limits:
interface FixedWindowRateLimit {
requests: number // Max requests
window: number // Time window in seconds
key: string // Rate limit key (user ID, API key)
}
// Example: 1000 requests per hour
{
requests: 1000,
window: 3600,
key: 'api_key_abc123'
}Sliding Window
More accurate, prevents burst at window boundaries:
interface SlidingWindowRateLimit {
requests: number
window: number
timestamps: number[] // Recent request timestamps
}
function checkSlidingWindow(limit: SlidingWindowRateLimit): boolean {
const now = Date.now()
const windowStart = now - (limit.window * 1000)
// Remove old timestamps
limit.timestamps = limit.timestamps.filter(ts => ts > windowStart)
// Check if under limit
if (limit.timestamps.length < limit.requests) {
limit.timestamps.push(now)
return true
}
return false
}Token Bucket
Allows bursts while maintaining average rate:
interface TokenBucketRateLimit {
capacity: number // Bucket size
refillRate: number // Tokens per second
tokens: number // Current tokens
lastRefill: number // Last refill timestamp
}
function checkTokenBucket(bucket: TokenBucketRateLimit): boolean {
const now = Date.now()
const elapsed = (now - bucket.lastRefill) / 1000
// Refill tokens
bucket.tokens = Math.min(
bucket.capacity,
bucket.tokens + (elapsed * bucket.refillRate)
)
bucket.lastRefill = now
// Consume token if available
if (bucket.tokens >= 1) {
bucket.tokens -= 1
return true
}
return false
}Tiered Rate Limits
Different limits per pricing tier:
interface TieredRateLimits {
free: {
requestsPerMinute: 10
requestsPerDay: 1000
concurrentRequests: 1
}
starter: {
requestsPerMinute: 100
requestsPerDay: 50000
concurrentRequests: 5
}
pro: {
requestsPerMinute: 1000
requestsPerDay: 1000000
concurrentRequests: 20
}
enterprise: {
requestsPerMinute: 10000
requestsPerDay: null // Unlimited
concurrentRequests: 100
}
}Rate Limit Headers
Communicate limits to clients via headers:
// Response headers
{
'X-RateLimit-Limit': '1000', // Max requests per window
'X-RateLimit-Remaining': '742', // Requests remaining
'X-RateLimit-Reset': '1640995200', // Unix timestamp when limit resets
'X-RateLimit-Window': '3600', // Window size in seconds
'Retry-After': '300' // Seconds until retry (on 429)
}
// 429 Too Many Requests response
{
"error": {
"type": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 300 seconds.",
"retryAfter": 300,
"limit": 1000,
"window": 3600
}
}Smart Rate Limiting
Advanced strategies for better UX:
interface SmartRateLimit {
// Adaptive limits based on user behavior
adaptive: {
baseLimit: number
multiplier: number // Increase for good actors
minLimit: number
maxLimit: number
}
// Burst allowance
burst: {
enabled: boolean
capacity: number // Extra requests allowed
refillRate: number // Refill per second
}
// Cost-based limiting
weighted: {
enabled: boolean
costs: Record<string, number> // Endpoint costs
budget: number // Total cost per window
}
}
// Example: Expensive endpoints cost more
const weightedLimits = {
'GET /users': 1,
'POST /ai/generate': 10,
'POST /ai/embeddings': 5
}Usage Tracking
Comprehensive usage tracking enables billing, analytics, and optimization.
Metrics to Track
interface UsageMetrics {
// Volume metrics
totalRequests: number
successfulRequests: number
failedRequests: number
// Latency metrics
averageLatency: number // Mean response time
p50Latency: number // Median
p95Latency: number // 95th percentile
p99Latency: number // 99th percentile
// Error metrics
errorRate: number // Percentage
errorsByType: Record<string, number>
errorsByEndpoint: Record<string, number>
// Resource metrics
bandwidthUsed: number // Bytes transferred
computeTime: number // CPU milliseconds
storageUsed: number // Bytes stored
// Business metrics
uniqueUsers: number
activeAPIKeys: number
billingAmount: number
}Real-time Event Tracking
interface UsageEvent {
id: string
timestamp: Date
apiKey: string
userId?: string
// Request details
method: string
endpoint: string
statusCode: number
latency: number
// Resource usage
requestSize: number // Bytes
responseSize: number // Bytes
computeTime?: number // Milliseconds
// Billing
cost?: number // For usage-based billing
billable: boolean
// Context
ip: string
userAgent: string
region?: string
}
// Track event
async function trackUsage(event: UsageEvent): Promise<void> {
// Real-time tracking (Redis, Kafka)
await redis.hincrby(`usage:${event.apiKey}:${today}`, 'requests', 1)
await redis.hincrby(`usage:${event.apiKey}:${today}`, 'bytes', event.responseSize)
// Long-term storage (database, warehouse)
await db.usageEvents.insert(event)
// Real-time analytics (ClickHouse, BigQuery)
await analyticsQueue.push(event)
}Usage Aggregation
interface UsageAggregation {
// Time-based aggregation
aggregateBy(
dimension: 'hour' | 'day' | 'week' | 'month',
startDate: Date,
endDate: Date
): Promise<AggregatedUsage[]>
// Dimension-based aggregation
groupBy(
dimensions: Array<'apiKey' | 'endpoint' | 'region' | 'user'>,
filters?: UsageFilters
): Promise<GroupedUsage[]>
}
// Example: Daily usage by endpoint
const dailyUsage = await usageAggregation.aggregateBy('day', startDate, endDate)
const byEndpoint = await usageAggregation.groupBy(['endpoint'])Usage Dashboards
<UsageDashboard apiKey={apiKey}>
<DateRangePicker />
<MetricCards>
<Card metric="totalRequests" />
<Card metric="successRate" />
<Card metric="averageLatency" />
<Card metric="billingAmount" />
</MetricCards>
<Charts>
<RequestsChart timeRange="7d" />
<LatencyChart percentiles={[50, 95, 99]} />
<EndpointBreakdown />
<ErrorRateChart />
</Charts>
<Tables>
<TopEndpoints limit={10} />
<RecentErrors limit={20} />
<SlowRequests threshold={1000} />
</Tables>
</UsageDashboard>Quota Management
interface UsageQuota {
limit: number // Max allowed
used: number // Currently used
period: 'minute' | 'hour' | 'day' | 'month'
resets: Date // When quota resets
notifyAt?: number[] // Alert at percentages [80, 95, 100]
}
// Check quota
function checkQuota(quota: UsageQuota, amount: number = 1): {
allowed: boolean
remaining: number
percentage: number
} {
const remaining = quota.limit - quota.used
const allowed = remaining >= amount
const percentage = (quota.used / quota.limit) * 100
// Send alerts if thresholds crossed
if (quota.notifyAt?.some(threshold => percentage >= threshold)) {
sendQuotaAlert(quota, percentage)
}
return { allowed, remaining, percentage }
}Webhook System
Webhooks enable real-time event notifications to customer systems.
Webhook Events
interface WebhookEvent {
id: string
type: string // 'payment.succeeded', 'user.created'
createdAt: Date
data: unknown // Event-specific payload
previousAttributes?: unknown // For update events
apiVersion: string // Event schema version
}
// Define available events
const webhookEvents = {
// Resource lifecycle
'resource.created': 'A new resource was created',
'resource.updated': 'A resource was updated',
'resource.deleted': 'A resource was deleted',
// Business events
'payment.succeeded': 'Payment completed successfully',
'payment.failed': 'Payment attempt failed',
'subscription.renewed': 'Subscription renewed',
'subscription.canceled': 'Subscription canceled',
// API events
'api.quota_exceeded': 'API quota limit reached',
'api.rate_limited': 'Rate limit triggered',
}Webhook Delivery
interface WebhookDelivery {
id: string
webhookId: string
eventId: string
url: string
// Delivery status
status: 'pending' | 'success' | 'failed' | 'retrying'
attempts: number
lastAttempt?: Date
nextRetry?: Date
// Request/response
request: {
headers: Record<string, string>
body: string
}
response?: {
statusCode: number
headers: Record<string, string>
body: string
duration: number
}
}
// Deliver webhook
async function deliverWebhook(
webhook: Webhook,
event: WebhookEvent
): Promise<WebhookDelivery> {
const payload = {
id: event.id,
type: event.type,
created: event.createdAt.getTime(),
data: event.data
}
const signature = signWebhook(payload, webhook.secret)
const response = await fetch(webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature,
'X-Webhook-ID': webhook.id,
'X-Event-ID': event.id,
'X-Event-Type': event.type
},
body: JSON.stringify(payload),
timeout: 10000 // 10 second timeout
})
return {
id: generateId(),
webhookId: webhook.id,
eventId: event.id,
url: webhook.url,
status: response.ok ? 'success' : 'failed',
attempts: 1,
response: {
statusCode: response.status,
headers: Object.fromEntries(response.headers),
body: await response.text(),
duration: response.duration
}
}
}Signature Verification
Ensure webhooks are authentic:
// Sign webhook payload
function signWebhook(payload: unknown, secret: string): string {
const timestamp = Date.now()
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`
const signature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex')
return `t=${timestamp},v1=${signature}`
}
// Verify webhook signature (customer implementation)
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
tolerance: number = 300 // 5 minutes
): boolean {
const [timestampPart, signaturePart] = signature.split(',')
const timestamp = parseInt(timestampPart.split('=')[1])
const expectedSignature = signaturePart.split('=')[1]
// Check timestamp tolerance
const now = Date.now()
if (Math.abs(now - timestamp) > tolerance * 1000) {
return false
}
// Verify signature
const signedPayload = `${timestamp}.${payload}`
const computedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex')
return computedSignature === expectedSignature
}Retry Strategy
interface WebhookRetryConfig {
maxAttempts: number // Default: 5
backoff: 'linear' | 'exponential'
initialDelay: number // Seconds
maxDelay: number // Max seconds between retries
timeoutPerAttempt: number // Seconds
}
// Exponential backoff: 1m, 5m, 25m, 2h, 10h
const retryDelays = [60, 300, 1500, 7200, 36000]
async function retryWebhook(delivery: WebhookDelivery): Promise<void> {
if (delivery.attempts >= 5) {
await markWebhookFailed(delivery)
return
}
const delay = retryDelays[delivery.attempts] * 1000
const nextRetry = new Date(Date.now() + delay)
await scheduleWebhookRetry(delivery.id, nextRetry)
}Webhook Management UI
<WebhookManagement apiKey={apiKey}>
<CreateWebhook>
<URLInput placeholder="https://example.com/webhooks" />
<EventSelector events={webhookEvents} />
<SecretGenerator />
<TestWebhook />
</CreateWebhook>
<WebhookList>
<WebhookCard>
<Status active={webhook.isActive} />
<URL url={webhook.url} />
<Events events={webhook.events} />
<Actions>
<EditButton />
<DisableButton />
<DeleteButton />
</Actions>
</WebhookCard>
</WebhookList>
<DeliveryLogs webhookId={webhook.id}>
<FilterByStatus />
<FilterByEvent />
<LogsTable>
<Column field="timestamp" />
<Column field="event" />
<Column field="status" />
<Column field="attempts" />
<Column field="duration" />
</LogsTable>
<RetryButton />
</DeliveryLogs>
</WebhookManagement>SDKs and Client Libraries
SDKs make API integration easier by providing idiomatic interfaces for each programming language. See the dedicated SDKs documentation for comprehensive details.
Supported Languages
Prioritize SDK development based on customer language preferences:
interface SDKSupport {
language: string
status: 'official' | 'community' | 'planned'
package: string
repository: string
documentation: string
maintainer: string
}
const sdks: SDKSupport[] = [
// Tier 1: Official SDKs
{ language: 'JavaScript/TypeScript', status: 'official', package: 'npm:acme-sdk', ... },
{ language: 'Python', status: 'official', package: 'pypi:acme', ... },
{ language: 'Go', status: 'official', package: 'go:github.com/acme/sdk', ... },
// Tier 2: Community SDKs
{ language: 'Ruby', status: 'community', package: 'gem:acme', ... },
{ language: 'PHP', status: 'community', package: 'composer:acme/sdk', ... },
// Tier 3: Planned
{ language: 'Java', status: 'planned', ... },
{ language: 'C#/.NET', status: 'planned', ... },
]SDK Features Checklist
| Feature | Priority | Description |
|---|---|---|
| Authentication | Required | API key management, automatic headers |
| Type Safety | Required | Full TypeScript/typed definitions |
| Error Handling | Required | Structured exceptions, retry logic |
| Auto-Retry | High | Automatic retry with exponential backoff |
| Pagination | High | Iterators/generators for lists |
| Streaming | Medium | Support for streaming responses |
| Webhooks | Medium | Signature verification helpers |
| Idempotency | Medium | Automatic idempotency keys |
| Logging | Low | Debug mode with request/response logs |
Quick Start Example
// Installation
// npm install acme-sdk
// Usage
import Acme from 'acme-sdk'
const acme = new Acme('sk_live_...')
// Create a product
const product = await acme.products.create({
name: 'My API Product',
type: 'API'
})
// List with automatic pagination
for await (const product of acme.products.list()) {
console.log(product.name)
}
// Error handling
try {
await acme.products.create({ name: 'Invalid' })
} catch (error) {
if (error instanceof Acme.ValidationError) {
console.error('Validation failed:', error.details)
}
}SDK Generation
Auto-generate SDKs from OpenAPI specs:
// Use tools like:
// - OpenAPI Generator
// - Swagger Codegen
// - Fern
// - Speakeasy
// Generate from OpenAPI spec
$ openapi-generator generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./sdk/typescript
// Customize with templates
$ fern generate \
--language typescript \
--output ./sdk/typescript \
--template custom-templateDocumentation Generation
Great documentation is essential for API adoption. Auto-generate docs from code and OpenAPI specs.
Documentation Types
| Type | Purpose | Tools |
|---|---|---|
| API Reference | Complete endpoint documentation | OpenAPI, Swagger, Redoc |
| Guides | Step-by-step tutorials | Markdown, MDX |
| Code Examples | Copy-paste snippets | Auto-generated |
| SDK Docs | Library-specific docs | TypeDoc, JSDoc, Sphinx |
| Changelog | Version history | Git commits, manual |
| Status Page | Uptime and incidents | Statuspage, custom |
OpenAPI Specification
Define your API in OpenAPI format:
openapi: 3.1.0
info:
title: Acme API
version: 1.0.0
description: Data enrichment and validation API
servers:
- url: https://api.acme.com/v1
description: Production
- url: https://api.sandbox.acme.com/v1
description: Sandbox
paths:
/products:
post:
summary: Create a product
operationId: createProduct
tags: [Products]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductCreate'
responses:
'201':
description: Product created
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: API Key
schemas:
Product:
type: object
required: [id, name, type]
properties:
id:
type: string
example: prod_abc123
name:
type: string
example: My Product
type:
type: string
enum: [SaaS, API, Data, Headless]Documentation Sites
Popular documentation frameworks:
// Fumadocs (Next.js based)
export default {
docs: {
source: './content/docs',
openapi: './openapi.yaml'
}
}
// Docusaurus (Meta)
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: { routeBasePath: '/' },
blog: false
}
]
],
plugins: ['docusaurus-plugin-openapi-docs']
}
// Mintlify
{
"name": "Acme API",
"openapi": ["./openapi.yaml"],
"navigation": [
{ "group": "API Reference", "pages": ["api-reference/**"] }
]
}Interactive API Explorer
<APIExplorer openapi={openApiSpec}>
<EndpointSelector />
<RequestBuilder>
<AuthenticationInput />
<ParametersForm />
<RequestBodyEditor language="json" />
</RequestBuilder>
<CodeGenerator languages={['curl', 'javascript', 'python']} />
<ResponseViewer>
<StatusCode />
<Headers />
<Body language="json" />
</ResponseViewer>
<TryItButton />
</APIExplorer>Auto-Generated Examples
// Generate code examples from OpenAPI
import { generateExamples } from 'openapi-to-examples'
const examples = generateExamples(openApiSpec, {
languages: ['curl', 'javascript', 'python', 'go'],
includeAuth: true,
includeHeaders: true
})
// Output:
{
curl: "curl -X POST https://api.acme.com/v1/products...",
javascript: "const acme = new Acme('sk_test_...')...",
python: "import acme\nclient = acme.Client('sk_test_...')...",
go: "client := acme.NewClient(\"sk_test_...\")\n..."
}Documentation Versioning
interface DocVersion {
version: string // 'v1', 'v2', 'v3'
status: 'stable' | 'beta' | 'deprecated'
releaseDate: Date
deprecationDate?: Date
sunsetDate?: Date // When version goes offline
changelog: string
migrationGuide?: string
}
// Version selector in docs
<VersionSelector>
<Version value="v3" status="stable" current />
<Version value="v2" status="deprecated" sunsetDate="2024-12-31" />
<Version value="v1" status="deprecated" sunsetDate="2024-06-30" />
</VersionSelector>API Versioning Strategies
Choose a versioning strategy that balances stability and innovation.
Versioning Approaches
URL Path Versioning
Most common, clear version in URL:
// Version in path
https://api.acme.com/v1/products
https://api.acme.com/v2/products
https://api.acme.com/v3/products
// Pros: Clear, cacheable, easy to route
// Cons: Duplicate endpoints, URL pollutionHeader Versioning
Version via header:
// Request
GET /products
Accept: application/vnd.acme.v2+json
// Or custom header
GET /products
X-API-Version: 2
// Pros: Clean URLs, flexible
// Cons: Less visible, harder to test in browserQuery Parameter Versioning
Version via query string:
// Request
GET /products?version=2
GET /products?api-version=2
// Pros: Easy to implement
// Cons: Caching issues, less commonContent Negotiation
Version via Accept header:
// Request
GET /products
Accept: application/vnd.acme.v2+json
// Response
Content-Type: application/vnd.acme.v2+json
// Pros: RESTful, supports multiple formats
// Cons: Complex, less intuitiveVersion Lifecycle
interface APIVersion {
version: string
// Lifecycle stages
beta: {
releaseDate: Date
features: string[]
breakingChanges: string[]
}
stable: {
releaseDate: Date
supportUntil: Date
}
deprecated: {
announcedDate: Date
sunsetDate: Date
migrationGuide: string
replacement: string // 'v3'
}
// Version metadata
changelog: string
documentation: string
sdkVersion?: string
}
// Example lifecycle
const v2Lifecycle: APIVersion = {
version: 'v2',
beta: {
releaseDate: new Date('2024-01-01'),
features: ['Bulk operations', 'Webhooks v2'],
breakingChanges: ['Removed /legacy endpoint']
},
stable: {
releaseDate: new Date('2024-03-01'),
supportUntil: new Date('2025-12-31')
},
deprecated: {
announcedDate: new Date('2025-06-01'),
sunsetDate: new Date('2025-12-31'),
migrationGuide: '/docs/migrate-v2-to-v3',
replacement: 'v3'
}
}Breaking vs Non-Breaking Changes
// Non-breaking changes (same version)
interface NonBreakingChanges {
// Safe to add
addNewEndpoint: boolean // New /products/bulk
addNewField: boolean // Add optional field to response
addNewQueryParam: boolean // Add optional filter
addNewHeader: boolean // Add optional header
// Safe to deprecate (with warnings)
deprecateField: boolean // Mark field as deprecated
deprecateEndpoint: boolean // Add deprecation header
}
// Breaking changes (new version required)
interface BreakingChanges {
removeEndpoint: boolean // DELETE /old-endpoint
removeField: boolean // Remove response field
changeFieldType: boolean // String to number
changeFieldName: boolean // Rename field
changeResponseStructure: boolean // Nested to flat
changeErrorFormat: boolean // Error schema change
requireNewAuth: boolean // Change auth mechanism
changeRateLimit: boolean // Reduce limits
}Deprecation Workflow
// 1. Announce deprecation
async function deprecateVersion(version: string, sunsetDate: Date) {
// Add deprecation header
res.setHeader('X-API-Deprecation', `Version ${version} deprecated`)
res.setHeader('X-API-Sunset', sunsetDate.toISOString())
res.setHeader('Link', '<https://docs.acme.com/migrate>; rel="deprecation"')
// Email users
await notifyUsers({
subject: `API ${version} Deprecation Notice`,
message: `Version ${version} will sunset on ${sunsetDate}`,
migrationGuide: '/docs/migrate'
})
// Log usage
await analytics.track('deprecated_version_used', {
version,
apiKey,
endpoint
})
}
// 2. Monitor usage during deprecation period
const usageByVersion = await analytics.query({
metric: 'api_requests',
groupBy: ['version'],
dateRange: 'last_30_days'
})
// 3. Sunset version
async function sunsetVersion(version: string) {
// Return 410 Gone
if (requestedVersion === version) {
return res.status(410).json({
error: {
type: 'version_sunset',
message: `API ${version} was sunset on ${sunsetDate}`,
migration: 'https://docs.acme.com/migrate',
currentVersion: 'v3'
}
})
}
}Migration Support
interface MigrationGuide {
fromVersion: string
toVersion: string
breakingChanges: Array<{
type: 'removed' | 'renamed' | 'changed'
endpoint?: string
field?: string
oldValue: string
newValue: string
migration: string
}>
// Auto-migration tools
codemod?: string // Automated code transformation
migrationScript?: string // CLI tool to migrate
// Support
slackChannel?: string
supportEmail?: string
office_hours?: string
}
// Example migration guide
const v2ToV3Migration: MigrationGuide = {
fromVersion: 'v2',
toVersion: 'v3',
breakingChanges: [
{
type: 'renamed',
endpoint: '/products',
field: 'product_type',
oldValue: 'product_type',
newValue: 'type',
migration: 'Rename all instances of product_type to type'
},
{
type: 'removed',
endpoint: '/legacy/products',
oldValue: '/legacy/products',
newValue: '/products',
migration: 'Use /products instead of /legacy/products'
}
],
codemod: 'npx @acme/migrate-v2-v3',
slackChannel: '#api-migration-help'
}Business Metrics
API-Specific KPIs
interface APIMetrics {
// Revenue
totalRevenue: number
revenuePerRequest: number
revenuePerDeveloper: number
// Usage
totalRequests: number
requestsPerSecond: number
uniqueAPIKeys: number
activeIntegrations: number
// Quality
uptime: number // 99.9%
p50Latency: number // 50th percentile
p99Latency: number // 99th percentile
errorRate: number // % of failed requests
// Growth
newDevelopers: number
retentionRate: number
expansionRate: number // Developers increasing usage
}Developer Lifecycle
1. Visitor → Sign Up
2. Developer → Get API Key
3. Developer → Make First Request
4. Developer → Integrate (production)
5. Developer → Scale Usage
6. Developer → Upgrade Tier (if applicable)Integration Points
Stripe Integration
// Usage-based billing via Stripe
const usageRecord = await stripe.subscriptionItems.createUsageRecord(
subscriptionItem.id,
{
quantity: requestCount, // Number of API requests
timestamp: Math.floor(Date.now() / 1000),
action: 'increment',
}
);MCP Tools
API products expose themselves as Claude tools:
// Auto-generated MCP tool definition
{
name: "acme_enrichment_lookup",
description: "Enrich contact data with company information",
inputSchema: {
type: "object",
properties: {
email: { type: "string", description: "Email address to enrich" }
},
required: ["email"]
}
}Examples
Payment API
name: PayFlow API
type: API
description: Accept payments globally with a single integration
endpoints:
- POST /v1/payments
- GET /v1/payments/:id
- POST /v1/refunds
- GET /v1/balance
pricing:
- 2.9% + $0.30 per successful transaction
- No monthly fees
- Volume discounts available
features:
- Accept 135+ currencies
- PCI compliance built-in
- Real-time webhooks
- Fraud detection
- Recurring billingEmail Validation API
name: ValidMail API
type: API
description: Real-time email validation and verification
endpoints:
- POST /v1/validate
- POST /v1/validate/bulk
- GET /v1/validate/:id
pricing:
- Free: 1,000 validations/month
- Basic: $0.001/validation (1,000+)
- Pro: $0.0005/validation (100,000+)
- Enterprise: Custom pricing
features:
- Syntax validation
- Domain verification
- Mailbox existence check
- Disposable email detection
- Risk scoringAI Text Generation API
name: TextGen API
type: API
description: Generate human-like text with AI
endpoints:
- POST /v1/completions
- POST /v1/chat/completions
- POST /v1/embeddings
pricing:
- $0.002 per 1K input tokens
- $0.004 per 1K output tokens
- Discounts at 10M+ tokens/month
features:
- Multiple AI models
- Streaming responses
- Fine-tuning support
- Function calling
- JSON modeNext Steps
- Product Schema - Product type definition
- REST APIs - API interface implementation
- MCPs - Model Context Protocol for AI agents
- SDKs - Client libraries for API products