Skip to main content

Best Boilerplates for Developer Tools and APIs in 2026

·StarterPick Team
Share:

TL;DR

Developer tools need a different stack than consumer SaaS. Your API is your product, your docs are your UX, and your SDK is your onboarding. The best 2026 starter: Hono + @hono/zod-openapi for the API, Unkey for API key management, Scalar for developer portal, and openapi-ts for client SDK generation. For Python APIs: FastAPI with its built-in OpenAPI generation. For TypeScript-first full-stack: tRPC with the OpenAPI adapter.

Key Takeaways

  • Documentation is the product: Developers choose APIs based on doc quality, not feature count
  • OpenAPI spec-first: Define routes with Zod → auto-generate spec → auto-generate SDKs
  • API key management: Use Unkey rather than rolling your own (per-key rate limits, analytics, revocation)
  • SDK generation: Auto-generate TypeScript/Python clients from your OpenAPI spec
  • Rate limiting: Two layers — IP-level (Upstash) and per-key (Unkey)
  • Versioning from day one: /v1/ prefix costs nothing to add, costs enormously to retrofit

Developer Tools Have Different Requirements

Building a developer-facing product has fundamentally different requirements from consumer SaaS. A B2C SaaS product succeeds on UX, pricing, and marketing. A developer tool succeeds on reliability, documentation quality, and integration ease.

Developers have unusually high churn tolerance for tools with good documentation and unusually low tolerance for tools with bad documentation. A developer who gets stuck for 2 hours because your docs don't cover their use case will switch to a competitor — even an inferior one — rather than submit a support ticket. Your documentation and API design are competitive advantages in a way that your UI almost never is.

The three things that determine whether a developer tool gets adopted:

  • Time to first API call: How long does it take a developer to make their first successful request? Under 5 minutes with an official SDK is the gold standard. Over 30 minutes means significant friction.
  • Error message quality: When something goes wrong (wrong API key, malformed request, exceeded rate limit), does the error message explain what happened and how to fix it? Good error messages prevent support tickets.
  • Self-service key management: Can developers create, rotate, and revoke their own API keys without contacting you? Developer tools that require email to rotate a compromised key are a dealbreaker for security-conscious teams.

Quick Comparison

StackOpenAPIRate LimitingSDK GenerationAuthBest For
Hono + OpenAPI✅ Auto✅ Cloudflare✅ AutoAPI keysEdge API product
FastAPI✅ Auto⚠️ Manual✅ openapi-generatorJWT/API keysPython API product
tRPC + OpenAPI✅ Via adapter⚠️ Manual✅ AutoJWTTypeScript API
Fastify + Swagger✅ Plugin✅ Plugin✅ AutoJWT/API keysNode.js API product

Hono + Zod OpenAPI — Best Edge API

import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';

const app = new OpenAPIHono();

const UserSchema = z.object({
  id: z.string().openapi({ example: 'user_123' }),
  name: z.string().openapi({ example: 'Alice Johnson' }),
  email: z.string().email().openapi({ example: 'alice@example.com' }),
}).openapi('User');

const route = createRoute({
  method: 'get',
  path: '/users/{id}',
  request: { params: z.object({ id: z.string() }) },
  responses: {
    200: {
      content: { 'application/json': { schema: UserSchema } },
      description: 'Returns user object',
    },
  },
});

app.openapi(route, async (c) => {
  const user = await getUser(c.req.valid('param').id);
  return c.json(user);
});

// Automatic OpenAPI spec at /doc
app.doc('/doc', { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' } });

// Automatic Swagger UI at /ui
app.get('/ui', swaggerUI({ url: '/doc' }));

Why this works for developer tools:

  • OpenAPI spec auto-generated from types
  • SDK generation via openapi-generator-cli from the spec
  • Runs on Cloudflare Workers (global edge, $0 idle)
  • Rate limiting via Cloudflare Workers KV

API Authentication for Developer Products

// API key authentication pattern
async function validateApiKey(apiKey: string): Promise<User | null> {
  const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(apiKey));
  const hashHex = Array.from(new Uint8Array(hash))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return db.apiKey.findFirst({
    where: { keyHash: hashHex, revokedAt: null },
    include: { user: true },
  });
}

// Middleware for API key auth
app.use('/api/*', async (c, next) => {
  const apiKey = c.req.header('X-API-Key') || c.req.header('Authorization')?.replace('Bearer ', '');
  if (!apiKey) return c.json({ error: 'API key required' }, 401);

  const user = await validateApiKey(apiKey);
  if (!user) return c.json({ error: 'Invalid API key' }, 401);

  c.set('user', user);
  await next();
});

API Key Management at Scale

Rolling your own API key system works for the first few hundred customers. At scale, you need revocation, per-key rate limits, key scoping (read-only vs read-write), and usage analytics per key. These requirements point to a dedicated API key management service.

Unkey is the leading option for TypeScript API products in 2026. It provides key generation, rate limiting per key, usage analytics, and key revocation through a single SDK — without requiring you to run any additional infrastructure:

// lib/unkey.ts
import { Unkey } from '@unkey/api';

const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });

// Generate a new API key for a user
export async function createApiKey(userId: string, keyName: string) {
  const { result, error } = await unkey.keys.create({
    apiId: process.env.UNKEY_API_ID!,
    prefix: 'sk',
    name: keyName,
    ownerId: userId,
    meta: { userId, plan: 'pro' },
    ratelimit: {
      type: 'sliding',
      limit: 1000,    // 1000 requests
      refillRate: 1000,
      refillInterval: 60000, // per minute
    },
    expires: undefined, // Never expires
  });

  if (error) throw new Error(error.message);
  return result; // result.key is the plaintext key (shown once), result.keyId for management
}

// Validate an incoming API key
export async function validateApiKey(key: string) {
  const { result, error } = await unkey.keys.verify({ key });
  if (error || !result.valid) return null;
  return result; // result.ownerId, result.meta, result.remaining
}

With Unkey, key validation is a single API call that handles rate limiting, expiry checking, and usage tracking simultaneously. The per-key rate limits let you offer different limits to free vs paid customers without any additional database infrastructure.


Rate Limiting Patterns

// Usage-based rate limiting with Redis
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(100, '1 m'),  // 100 req/minute
  prefix: 'api_rl',
});

app.use('/api/*', async (c, next) => {
  const { success, remaining, reset } = await ratelimit.limit(c.get('user').id);

  c.header('X-RateLimit-Remaining', remaining.toString());
  c.header('X-RateLimit-Reset', reset.toString());

  if (!success) return c.json({ error: 'Rate limit exceeded' }, 429);
  await next();
});

SDK Generation

Auto-generate SDKs from your OpenAPI spec:

# Generate TypeScript SDK
npx @openapitools/openapi-generator-cli generate \
  -i https://api.yourtool.com/doc \
  -g typescript-fetch \
  -o ./sdks/typescript

# Generate Python SDK
npx @openapitools/openapi-generator-cli generate \
  -i https://api.yourtool.com/doc \
  -g python \
  -o ./sdks/python

This creates official client libraries that reduce integration friction dramatically.


API Versioning Strategy

API versioning decisions made at launch are expensive to change later. The three approaches and their tradeoffs:

URL versioning (/v1/users, /v2/users): The most common approach. Explicit, cache-friendly, easy to test in a browser. The downside is URL proliferation — after three major versions, you have three live sets of endpoints to maintain. For developer tools where breaking changes are infrequent and well-communicated, this is the correct default.

Header versioning (API-Version: 2026-01-01 or Accept: application/vnd.myapi.v2+json): Keeps URLs clean but makes testing harder (you can't just paste a URL into a browser). Used by Stripe (date-based header versioning) and some enterprise APIs. The date-based approach Stripe uses is elegant: each API key has a default version, and clients can opt in to newer versions explicitly. This lets you ship breaking changes without forcing immediate migration.

No versioning (additive only): Commit to never making breaking changes — only adding fields, never removing or changing them. This works if your API surface is small and you have strong discipline. It breaks down when you need to rename a field or change a response shape. Reserve this approach for internal APIs where you control all consumers.

The practical recommendation for developer tools launching in 2026: URL versioning with /v1/ prefix from day one. It costs nothing to add initially and costs enormously to retrofit. Every route you build gets the /v1/ prefix even if there's no v2 in sight.


Monitoring and Observability

Developer tools need more observability than typical SaaS products because debugging a customer's integration failure requires visibility into both the request and the API's internal processing:

Request logging: Log every API request with request ID, method, path, status code, latency, and the authenticated user/key. This lets you answer "why did this customer's request fail at 3pm yesterday?" without requiring them to reproduce it.

// Structured request logging middleware
app.use('/api/*', async (c, next) => {
  const requestId = crypto.randomUUID();
  const start = Date.now();

  c.header('X-Request-Id', requestId);

  await next();

  console.log(JSON.stringify({
    requestId,
    method: c.req.method,
    path: new URL(c.req.url).pathname,
    status: c.res.status,
    latencyMs: Date.now() - start,
    userId: c.get('user')?.id,
    apiKeyId: c.get('apiKey')?.id,
  }));
});

Error rate alerts: Track error rates per API key and per endpoint. A customer whose error rate spikes from 0% to 40% has a bug in their integration — proactively reaching out before they file a support ticket is a powerful retention tool.

Usage dashboards: Show authenticated users their own request history, error rates, and usage against their rate limits. Self-service visibility prevents "why am I getting rate limited?" support tickets.


Documentation Strategy

An OpenAPI spec gives you machine-readable documentation. Good developer tool documentation also needs prose:

Quickstart guide: A 5-minute path from "I just found this API" to "I made my first successful request." Include the exact commands to install the SDK, the minimum code snippet for the most common use case, and a link to the full reference. Keep it under 500 words — developers skim quickstarts.

Authentication guide: A dedicated page explaining how to get an API key, where to put it in requests, how to scope keys (if applicable), and how to rotate or revoke them. This is the most common support question if your docs don't answer it clearly.

Error reference: A page listing all possible error codes, what causes them, and how to fix them. RATE_LIMIT_EXCEEDED: You've made too many requests. Retry after the X-RateLimit-Reset timestamp. is actionable. 429 Too Many Requests is not.

Code examples in multiple languages: The same endpoint call in TypeScript, Python, and cURL covers 80%+ of your users. Auto-generating these from your OpenAPI spec with tools like Scalar's code sample generator keeps examples in sync with your actual API.


Webhooks: Pushing Events to Customers

A complete developer tool API sends webhooks — HTTP POST requests to customer-configured URLs when events occur in your system. Webhooks let customers build integrations without polling your API.

// lib/webhooks.ts
interface WebhookPayload {
  id: string;
  type: string;
  data: unknown;
  createdAt: string;
}

export async function sendWebhook(endpoint: WebhookEndpoint, payload: WebhookPayload) {
  const body = JSON.stringify(payload);

  // Sign the payload so customers can verify it's from you
  const signature = await computeHmacSignature(body, endpoint.signingSecret);

  const response = await fetch(endpoint.url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Signature': signature,
      'X-Webhook-Id': payload.id,
      'X-Webhook-Timestamp': payload.createdAt,
    },
    body,
    signal: AbortSignal.timeout(5000), // 5 second timeout
  });

  // Store delivery attempt for debugging
  await prisma.webhookDelivery.create({
    data: {
      webhookEndpointId: endpoint.id,
      payloadId: payload.id,
      statusCode: response.status,
      success: response.ok,
    },
  });

  return response.ok;
}

// Retry failed webhooks with exponential backoff (via queue or cron)
export async function retryFailedWebhooks() {
  const failed = await prisma.webhookDelivery.findMany({
    where: { success: false, retries: { lt: 5 } },
    include: { webhookEndpoint: true },
  });

  for (const delivery of failed) {
    const backoffMs = Math.pow(2, delivery.retries) * 1000; // 1s, 2s, 4s, 8s, 16s
    // ... retry after backoff
  }
}

The HMAC signature is the key security element. Customers verify the signature using their signing secret to confirm the webhook is genuinely from your service and hasn't been tampered with. Svix is a managed webhook delivery service that handles retry logic, delivery logs, and a webhook portal for customers — worth evaluating before building webhook infrastructure from scratch.


API Design Principles for Longevity

Decisions you make in v1 of your API are difficult to reverse once developers have integrated. The patterns that prevent painful breaking changes:

Envelope responses: Return { data: {...}, meta: { requestId, timestamp } } rather than the resource directly. Envelope responses let you add metadata fields without breaking existing consumers who destructure data.

Consistent resource IDs: Choose a prefix convention early: user_abc123, org_xyz789. Prefixed IDs make logs readable and prevent accidentally using IDs interchangeably. Stripe's ID format is the reference implementation.

Idempotency keys: For write operations, accept an Idempotency-Key header. If the same key is used within 24 hours, return the cached response instead of processing again. Prevents duplicate charges and duplicate records when clients retry failed requests.

Deprecation headers: When removing a field or endpoint in v2, add a Deprecation response header to v1 responses pointing to the migration guide. Give developers at least 6 months warning before removing anything.


Testing API Products

Developer tools need more comprehensive testing than typical SaaS apps because your API is a public contract — breaking it affects your customers' code in production.

Contract testing: Verify that your API responses match your documentation and client SDK types. If your OpenAPI spec says user.id is a string, your tests should assert that. Tools like zod can generate schemas from your OpenAPI spec and validate every response:

// test/api-contracts.test.ts
import { describe, it, expect } from 'vitest';
import { UserSchema } from '../schemas';

describe('GET /api/v1/users/:id', () => {
  it('returns valid user schema', async () => {
    const response = await apiClient.get('/api/v1/users/user_123');
    expect(response.status).toBe(200);

    // Assert response matches schema exactly
    const result = UserSchema.safeParse(await response.json());
    expect(result.success).toBe(true);
    if (!result.success) {
      throw new Error(`Schema mismatch: ${JSON.stringify(result.error.format())}`);
    }
  });
});

Rate limit testing: Verify that rate limits are applied correctly and return the proper headers. A customer who discovers rate limits through production failures rather than clear documentation and predictable behavior will churn.

Idempotency testing: For write operations with idempotency keys, verify that the same key returns the same response on repeat calls without creating duplicate records.

Running these tests against a real database (not mocks) catches bugs that mock-based tests miss. The investment pays off every time you avoid a breaking change reaching a customer's integration.


Building a Developer Community

Developer tools grow through community, not advertising. The channels that work:

Discord or Slack: A community server for API users lets developers help each other, surfaces common integration problems you should document, and creates a feedback channel that's faster than support tickets. A small active community is more valuable than a large inactive one.

Changelog with code examples: Every API update — new endpoint, deprecated field, performance improvement — should have a changelog entry with code snippets showing what changed. Developers subscribe to changelogs; they don't subscribe to marketing newsletters.

GitHub presence: An open-source SDK on GitHub with a responsive maintainer (responding to issues within 48 hours) is a powerful trust signal. Developers evaluate SDKs by their GitHub activity before integrating.

Postman / Bruno collections: Publish a Postman collection or Bruno workspace with pre-configured requests for all your endpoints. Developers who can import your collection and see working examples integrate 2-3x faster than those who build requests from documentation alone.


DX Checklist for Developer Tools

Before launching, audit your developer experience against this checklist. It covers the difference between tools developers adopt eagerly and tools they abandon after the first integration attempt:

  • API key visible in dashboard within 60 seconds of signup
  • First successful API call documented in under 5 steps
  • Rate limit headers present on every response (X-RateLimit-Remaining, X-RateLimit-Reset)
  • Every error response includes a human-readable message and an error code
  • Webhook signatures documented with verification code examples
  • OpenAPI spec available at a stable URL and versioned with the API
  • SDK available for at least TypeScript and Python
  • Changelog published for all breaking changes with 6-month deprecation window

This list represents the minimum viable DX for a developer tool that wants to compete with established players. Each item prevents a specific category of developer frustration or support ticket.


For the full implementation of the Hono + Unkey + Stripe Meters stack including SDK generation and monitoring, best boilerplates for building API products covers the complete production setup. For MCP server boilerplates — exposing your developer tool as tools for AI assistants, best MCP server boilerplates covers the integration patterns. For usage-based billing specific to API products with per-request pricing, usage-based billing with Stripe covers the Stripe Meters implementation.


Methodology

Stack comparisons based on direct evaluation of Hono, FastAPI, tRPC, and Fastify OpenAPI plugins as of Q1 2026. DX assessments based on time-to-first-API-call measurements. API design principles sourced from Stripe's API design documentation and Anthropic's API reference.

Compare developer tools and API boilerplates on StarterPick.

Check out this boilerplate

View Hono + OpenAPIon StarterPick →

The SaaS Boilerplate Matrix (Free PDF)

20+ SaaS starters compared: pricing, tech stack, auth, payments, and what you actually ship with. Updated monthly. Used by 150+ founders.

Join 150+ SaaS founders. Unsubscribe in one click.