mdxui

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

AspectValue
Revenue ModelUsage-based (pay-per-call, pay-per-request)
PricingPer API call, per GB transferred, tiered volume
Primary InterfaceREST API, GraphQL, tRPC
Target UserDevelopers (programmatic integration)
Developer ExperienceDocs, playground, SDKs, code examples
Example ProductsStripe 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:

SectionContent
QuickstartGet started in 5 minutes
AuthenticationAPI keys, OAuth, JWT
API ReferenceAll endpoints, parameters, responses
Code ExamplesRequest/response examples in multiple languages
SDKsClient libraries (JS, Python, Go, etc.)
Rate LimitsUsage limits and quotas
WebhooksEvent notifications
ErrorsError 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_abcd1234efgh5678ijkl9012mnop3456

Key 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

PracticeImplementation
Prefix KeysUse sk_test_ for test, sk_live_ for production
Hash StorageNever store plaintext keys, use bcrypt/argon2
Show OnceDisplay full key only on creation
ExpirationOptional TTL for temporary access
Audit LogsTrack all key usage and changes
RevocationInstant key disable capability
IP WhitelistingRestrict 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

FeaturePriorityDescription
AuthenticationRequiredAPI key management, automatic headers
Type SafetyRequiredFull TypeScript/typed definitions
Error HandlingRequiredStructured exceptions, retry logic
Auto-RetryHighAutomatic retry with exponential backoff
PaginationHighIterators/generators for lists
StreamingMediumSupport for streaming responses
WebhooksMediumSignature verification helpers
IdempotencyMediumAutomatic idempotency keys
LoggingLowDebug 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-template

Documentation Generation

Great documentation is essential for API adoption. Auto-generate docs from code and OpenAPI specs.

Documentation Types

TypePurposeTools
API ReferenceComplete endpoint documentationOpenAPI, Swagger, Redoc
GuidesStep-by-step tutorialsMarkdown, MDX
Code ExamplesCopy-paste snippetsAuto-generated
SDK DocsLibrary-specific docsTypeDoc, JSDoc, Sphinx
ChangelogVersion historyGit commits, manual
Status PageUptime and incidentsStatuspage, 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 pollution

Header 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 browser

Query Parameter Versioning

Version via query string:

// Request
GET /products?version=2
GET /products?api-version=2

// Pros: Easy to implement
// Cons: Caching issues, less common

Content 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 intuitive

Version 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 billing

Email 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 scoring

AI 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 mode

Next Steps

  • Product Schema - Product type definition
  • REST APIs - API interface implementation
  • MCPs - Model Context Protocol for AI agents
  • SDKs - Client libraries for API products

On this page