Skip to main content

Best Boilerplates for Building API Products 2026

·StarterPick Team
Share:

TL;DR

Building an API product (not just an API) means handling auth, rate limiting, usage billing, versioning, and auto-generated docs — the boring parts take 80% of the time. The best 2026 approach: Hono + OpenAPI spec-first with @hono/zod-openapi, API keys via Unkey, rate limiting via Upstash Ratelimit, and usage billing via Stripe Meters. For GraphQL products: Pothos schema builder + Yoga server. The Hono + Unkey combination has become the default API-as-a-product stack in 2026.

Key Takeaways

  • Hono + zod-openapi: Define routes with Zod schemas → auto-generate OpenAPI spec + Swagger UI
  • Unkey: Managed API key service (create, revoke, rate limit, usage analytics per key)
  • Upstash Ratelimit: Serverless Redis-based rate limiting with sliding window algorithm
  • Stripe Meters: Usage-based billing for API calls (per-request or per-token pricing)
  • Versioning: Route-based (/v1/, /v2/) or header-based (API-Version: 2026-01)
  • Docs generation: OpenAPI → Scalar or Redoc for developer portals

What Makes an API Product Different from an Internal API

Most SaaS products have APIs for internal use — their frontend calls their backend. An API product is when the API itself is the product: developers pay to query your data, trigger your features, or use your AI capabilities directly.

The distinction matters because internal APIs can cut corners. An API product cannot. External developers depend on your API's stability, documentation, error messages, and rate limits. A breaking change without a deprecation period destroys trust. Poor error messages create support burden. Missing rate limit headers frustrate experienced integrators.

API products require an additional layer of infrastructure above a normal REST API:

  • Developer identity: Every request is tied to an API key, not a session cookie
  • Rate limiting per key: Different keys get different limits based on plan
  • Usage billing: Customers pay based on what they use, not a fixed seat price
  • Self-service key management: Developers create, rotate, and revoke their own keys
  • Documentation portal: Machine-readable OpenAPI spec plus human-readable guides
  • Versioning and deprecation: A path for evolving the API without breaking existing integrations

The Hono + Unkey + Upstash + Stripe Meters stack handles all of these. The combination is purpose-built for API-as-a-product use cases in 2026.


Boilerplate Options for API Products

There is no single "API product boilerplate" that ships fully wired. Most teams assemble the stack from composable pieces. Here is how the main options compare:

ApproachCostSetup TimeBest For
Hono + Unkey + UpstashFree OSS + paid services2-3 daysGreenfield API products
T3 Stack + custom API layerFree3-5 daysTeams already on T3 with Next.js frontend
ShipFast + Hono API$2992 daysAPI product + SaaS dashboard combo
Hono starter (bare)Free4-6 daysMaximum control, full custom setup
Fastify + custom authFree5-7 daysHigh-throughput, more infra experience

For most API products being built in 2026, the recommended starting point is creating a Hono project with npm create hono@latest, then layering in Unkey for key management and Upstash for rate limiting. This gets the core infrastructure running in 2 days rather than the 5-7 days of building everything from scratch.


The API Product Stack

Hono (routing) 
  + @hono/zod-openapi (schema + OpenAPI)
  + Unkey (API keys + rate limits per key)
  + Upstash Ratelimit (IP-level rate limiting)
  + Stripe Meters (usage billing)
  + Scalar (dev portal / docs UI)

Hono + OpenAPI: Spec-First API

npm create hono@latest my-api
cd my-api
npm install @hono/zod-openapi @hono/swagger-ui zod
npm install @unkey/api @upstash/ratelimit @upstash/redis
// src/routes/packages.ts — spec-first route definition:
import { createRoute, z } from '@hono/zod-openapi';
import { OpenAPIHono } from '@hono/zod-openapi';

const PackageSchema = z.object({
  name: z.string().openapi({ example: 'react' }),
  version: z.string().openapi({ example: '18.3.0' }),
  weeklyDownloads: z.number().openapi({ example: 25000000 }),
  description: z.string().nullable(),
}).openapi('Package');

const getPackageRoute = createRoute({
  method: 'get',
  path: '/packages/{name}',
  tags: ['Packages'],
  summary: 'Get package details',
  request: {
    params: z.object({ name: z.string() }),
    query: z.object({
      period: z.enum(['1w', '1m', '3m', '1y']).optional().default('1m'),
    }),
  },
  responses: {
    200: {
      content: { 'application/json': { schema: PackageSchema } },
      description: 'Package details',
    },
    404: {
      content: {
        'application/json': {
          schema: z.object({ error: z.string() }),
        },
      },
      description: 'Package not found',
    },
    429: {
      content: {
        'application/json': {
          schema: z.object({ error: z.string(), retryAfter: z.number() }),
        },
      },
      description: 'Rate limit exceeded',
    },
  },
});

const router = new OpenAPIHono();

router.openapi(getPackageRoute, async (c) => {
  const { name } = c.req.valid('param');
  const { period } = c.req.valid('query');
  
  const pkg = await getPackageDetails(name, period);
  if (!pkg) return c.json({ error: 'Package not found' }, 404);
  
  return c.json(pkg, 200);
});

API Key Authentication with Unkey

// middleware/apiKey.ts:
import { Unkey } from '@unkey/api';
import { createMiddleware } from 'hono/factory';

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

export const apiKeyAuth = createMiddleware(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. Get one at dashboard.yourdomain.com' }, 401);
  }
  
  const { result, error } = await unkey.keys.verify({ key: apiKey });
  
  if (error || !result.valid) {
    return c.json({ 
      error: result?.code === 'RATE_LIMITED' 
        ? 'Rate limit exceeded' 
        : 'Invalid API key' 
    }, result?.code === 'RATE_LIMITED' ? 429 : 401);
  }
  
  // Attach key metadata to context:
  c.set('apiKeyId', result.keyId);
  c.set('userId', result.ownerId);
  c.set('plan', result.meta?.plan ?? 'free');
  c.set('remaining', result.remaining);
  
  await next();
});
// Creating API keys for users (in your dashboard):
import { Unkey } from '@unkey/api';

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

async function createApiKey(userId: string, plan: 'free' | 'pro' | 'enterprise') {
  const limits = {
    free: { ratelimit: { type: 'fast', limit: 100, refillRate: 100, refillInterval: 60000 } },
    pro: { ratelimit: { type: 'fast', limit: 1000, refillRate: 1000, refillInterval: 60000 } },
    enterprise: null,  // No limit
  };
  
  const { result } = await unkey.keys.create({
    apiId: process.env.UNKEY_API_ID!,
    ownerId: userId,
    meta: { plan },
    prefix: 'pk',
    ...limits[plan] && { ratelimit: limits[plan].ratelimit },
  });
  
  return result!.key;  // Return the key to show to user ONCE
}

Usage-Based Billing with Stripe Meters

// lib/billing.ts:
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// Record API usage for billing:
export async function recordApiUsage(
  stripeCustomerId: string, 
  endpoint: string,
  quantity: number = 1
) {
  await stripe.billing.meterEvents.create({
    event_name: 'api_requests',
    payload: {
      stripe_customer_id: stripeCustomerId,
      value: String(quantity),
    },
    identifier: `${stripeCustomerId}-${Date.now()}`,  // Idempotency
  });
}

// middleware/usageTracking.ts:
export const trackUsage = createMiddleware(async (c, next) => {
  await next();
  
  // Only track successful responses:
  if (c.res.status >= 200 && c.res.status < 300) {
    const userId = c.get('userId');
    const endpoint = c.req.routePath;
    
    // Fire and forget — don't block response:
    if (userId) {
      const stripeId = await getUserStripeId(userId);
      recordApiUsage(stripeId, endpoint).catch(console.error);
    }
  }
});

Rate Limiting with Upstash

// middleware/rateLimit.ts (IP-level, before API key check):
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'),  // 10 req per 10 seconds globally
  analytics: true,
  prefix: '@upstash/ratelimit',
});

export const ipRateLimit = createMiddleware(async (c, next) => {
  const ip = c.req.header('x-forwarded-for')?.split(',')[0] ?? 'anonymous';
  
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);
  
  c.header('X-RateLimit-Limit', String(limit));
  c.header('X-RateLimit-Remaining', String(remaining));
  c.header('X-RateLimit-Reset', String(reset));
  
  if (!success) {
    return c.json({
      error: 'Too many requests',
      retryAfter: Math.ceil((reset - Date.now()) / 1000),
    }, 429);
  }
  
  await next();
});

OpenAPI Docs with Scalar

// src/app.ts — full app setup:
import { OpenAPIHono } from '@hono/zod-openapi';
import { apiReference } from '@scalar/hono-api-reference';
import { packagesRouter } from './routes/packages';
import { apiKeyAuth } from './middleware/apiKey';
import { ipRateLimit } from './middleware/rateLimit';
import { trackUsage } from './middleware/usageTracking';

const app = new OpenAPIHono();

// Global middleware:
app.use('*', ipRateLimit);
app.use('/v1/*', apiKeyAuth);
app.use('/v1/*', trackUsage);

// Routes:
app.route('/v1', packagesRouter);

// OpenAPI spec endpoint:
app.doc('/openapi.json', {
  openapi: '3.1.0',
  info: {
    version: '1.0.0',
    title: 'My API',
    description: 'API for developers',
  },
  servers: [
    { url: 'https://api.yourdomain.com', description: 'Production' },
    { url: 'http://localhost:3000', description: 'Local' },
  ],
});

// Scalar developer docs:
app.get('/docs', apiReference({ spec: { url: '/openapi.json' } }));

export default app;

Versioning Strategy

// Route-based versioning (recommended):
const v1Router = new OpenAPIHono();
const v2Router = new OpenAPIHono();

app.route('/v1', v1Router);
app.route('/v2', v2Router);

// Deprecation headers:
v1Router.use('*', async (c, next) => {
  await next();
  c.header('Deprecation', 'true');
  c.header('Sunset', 'Sat, 01 Jan 2027 00:00:00 GMT');
  c.header('Link', '</v2/packages>; rel="successor-version"');
});

API Monetization Patterns

Pricing an API product requires a different mental model than SaaS seat pricing. Developers expect to pay proportionally to usage, not a flat fee. The three monetization patterns that work in 2026:

Pay-per-request: Charge a flat fee per API call ($0.001–$0.01 per request depending on compute cost). Works well for simple lookups and data APIs. Use Stripe Meters with aggregate_usage: sum. The key risk: unpredictable invoices cause customer churn. Offer a free tier with a hard request cap to let developers test without billing surprises.

Tiered monthly with overage: Free tier (100 requests/month) → Pro at $29/month (10,000 requests) → Enterprise (custom). Overages billed at $0.003 per request above the limit. This is the most common micro-SaaS API pricing model because it has predictable MRR with upside from heavy users.

Per-token or per-unit pricing: For AI APIs, LLM outputs, or storage-based APIs, charge on a natural unit. Stripe's tiered pricing supports graduated billing (first 1,000 tokens free, then $0.002 per token) out of the box.

The Unkey API key service provides usage analytics per key, which makes it easy to show developers their own usage breakdown without building a custom analytics dashboard.


Developer Experience: What Good Looks Like

API product success depends as much on developer experience as on the API itself. The baseline expectations for API products in 2026:

Error messages: Every 4xx response must explain why it failed and how to fix it. {"error": "Invalid API key"} is unhelpful. {"error": "Invalid API key", "code": "INVALID_KEY", "docs": "https://docs.yourdomain.com/auth"} is actionable.

Rate limit headers: Every response from a rate-limited endpoint must include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Developers integrate these into their retry logic. Missing them is a red flag.

SDK generation: Generate TypeScript and Python SDKs from your OpenAPI spec using openapi-typescript or hey-api/openapi-ts. Providing an SDK reduces integration time from hours to minutes and becomes a competitive moat.

Sandbox environment: A sandbox API key that returns mocked data without billing charges lets developers test integration before going live. Unkey supports separate key prefixes per environment.

Changelog: A dated changelog of API changes, especially deprecations, is a professional signal. Developers with production integrations check changelogs before upgrading.


API Product Feature Checklist

Core (must have):
  ✅ API key authentication (Unkey)
  ✅ Rate limiting per key + IP (Upstash)
  ✅ OpenAPI spec + Swagger/Scalar docs
  ✅ Versioned routes (/v1/, /v2/)
  ✅ Consistent error format { error, code, details }

Growth (recommended):
  ✅ Usage billing (Stripe Meters)
  ✅ Usage dashboard for API key holders
  ✅ SDK generation from OpenAPI spec (openapi-ts)
  ✅ Webhook events for async operations
  ✅ Status page (Statuspage.io or BetterStack)

Enterprise (later):
  ⬜ IP allowlisting per key
  ⬜ Audit logs
  ⬜ SSO / SAML authentication
  ⬜ Private API deployment
  ⬜ SLA tiers with dedicated rate limits

GraphQL API Products

Not all API products are REST. GraphQL offers a self-documenting API surface where clients request exactly the fields they need. For API products with complex, relational data models — where clients frequently need to fetch multiple related entities in a single request — GraphQL can be a better fit than REST.

The production GraphQL stack for TypeScript in 2026: Pothos for schema definition, GraphQL Yoga for the server, and Stellate for GraphQL-specific CDN caching. Pothos uses a code-first schema builder with strong TypeScript inference, eliminating the type mismatch between your schema and resolvers.

Authentication for GraphQL API products uses the same Unkey pattern as REST — verify the key in an HTTP middleware before the request reaches the GraphQL layer. Rate limiting is trickier: REST rate limits map naturally to endpoint calls, but a single GraphQL query can request 50 fields. Use query complexity analysis (Yoga's built-in plugin) to assign a cost to each query and rate limit on total complexity rather than request count.

Documentation for GraphQL products is easier than REST — GraphQL's introspection API lets any GraphQL IDE (GraphiQL, Apollo Sandbox, Altair) auto-discover your schema. Expose a /graphql endpoint with introspection enabled in development and disabled in production (it leaks your schema to competitors).


SDK Generation from Your OpenAPI Spec

Once your OpenAPI spec exists, generating SDKs for TypeScript, Python, and Go becomes a build step, not a project. Tools like openapi-typescript (TypeScript types) and hey-api/openapi-ts (full SDK with fetch client) generate fully typed clients from your spec file.

npx openapi-typescript https://api.yourdomain.com/openapi.json -o src/types/api.d.ts

A generated TypeScript SDK is a competitive advantage for developer-facing API products. It reduces integration time from a few hours (reading docs, writing fetch calls, handling types manually) to under 30 minutes (install package, call typed methods). The SDK becomes your product's onboarding experience.

Publish the SDK as a versioned npm package tied to your API version. When you release v2 of your API, publish a v2 SDK alongside it. Keep v1 on a maintenance branch. This gives developers a clear upgrade path rather than a breaking change they discover at runtime.


Monitoring and Observability for API Products

External developers depend on your API's reliability. Unlike an internal API where a 30-minute outage is an inconvenience, a 30-minute outage on your API product breaks your customers' production systems. You need observability before you need customers.

Structured logging: Every request should log the API key ID, user ID, endpoint, status code, and response time. Don't log the full API key (security). Use Axiom or Loki for log aggregation. Being able to search "all requests from key pk_abc123 in the last hour" is the first thing you'll need when a developer reports an issue.

Error rate alerting: Alert when your 5xx rate exceeds 1% for more than 5 minutes. BetterStack and Datadog both integrate with Hono via middleware. Set up a status page on BetterStack Uptime before launch — it signals reliability to developers evaluating your API.

Latency percentiles: Track p50, p95, and p99 latency per endpoint. p50 tells you typical performance; p99 tells you what the slowest 1% of requests experience. API products with consistently high p99 latency create unreliable integrations on the developer's side.

Per-key usage analytics: Unkey provides per-key usage analytics out of the box. Surface this data in your developer dashboard so API key holders can see their own usage patterns, rate limit status, and billing projections. Developers who can see their own usage are less likely to file support tickets.


Testing API Products

API products require testing at a different level than internal APIs. Your consumers are external developers who cannot read your source code, so if a breaking change slips through, there is no easy fix.

Contract testing: Validate that your running API matches your OpenAPI spec using tools like prism (stop-light/prism). Running prism validate against your spec after each deploy catches response shape mismatches before external developers encounter them.

Consumer-driven contract tests: If you have external consumers in beta, Pact lets them define the contract they depend on. Your CI pipeline validates that your API still satisfies those contracts before merging changes.

Rate limit behavior tests: Test that your rate limit headers are correct, that the retry-after value is accurate, and that requests after the limit return 429 (not 500). This behavior is easy to break inadvertently when refactoring middleware.


For adding AI features to your API product, including streaming endpoints and token-based billing, see how to add AI features to any SaaS boilerplate. For usage-based billing patterns beyond API calls (per-seat, per-document, per-storage), usage-based billing with Stripe covers the full metered billing workflow. For MCP server boilerplates — exposing your API as tools to AI assistants — see best MCP server boilerplates.


API Product Deployment

API products have different operational requirements than SaaS frontends. A few deployment considerations specific to API products:

Edge deployment: Hono is edge-compatible and deploys to Cloudflare Workers, Vercel Edge Functions, or Deno Deploy. Edge deployment puts your API compute close to your users, reducing latency from 100-200ms (US server responding to EU request) to 20-40ms (nearest edge node responding). For latency-sensitive API products, edge deployment is a meaningful advantage.

Caching: GET endpoints with stable data can be cached aggressively. Cloudflare KV or Upstash Redis serve as edge caches. Cache at the Unkey key + endpoint + parameter level so different API key holders get appropriately isolated cached responses.

Zero-downtime deploys: Unlike SaaS apps where users can handle a 5-second deploy restart, API consumers expect consistent availability. Use Cloudflare Workers or Vercel serverless (which are inherently zero-downtime by design). If deploying to traditional Node.js hosting, use PM2 cluster mode with zero-downtime reloads.


Methodology

Stack recommendations based on the Hono and Unkey documentation (2026 versions) and community usage patterns in the developer tooling space. Pricing estimates based on Unkey, Upstash, and Stripe Meters published pricing as of Q1 2026. The API product space has grown significantly in 2025 driven by the MCP (Model Context Protocol) ecosystem and AI agent tooling — developers building API products today should evaluate whether exposing their API as an MCP server is a product opportunity, as this has become a meaningful distribution channel for developer tools and data APIs.

Find API product boilerplates at 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.