Building API-First Products with open-source tools
Building an API-first product requires transitioning from internal endpoints to a public-facing infrastructure that prioritizes developer experience, security, and monetization. This guide outlines the steps to implement a robust API product lifecycle including contract-first design, secure key management, and usage-based billing integration.
Define the API Contract (OpenAPI First)
Before writing implementation code, define your API structure in an OpenAPI 3.1 YAML file. This serves as the single source of truth for your documentation, client generation, and validation. Ensure every endpoint has clear 'summary', 'operationId', and 'responses' fields defined.
openapi: 3.1.0
info:
title: AI Wrapper API
version: 1.0.0
paths:
/v1/generate:
post:
summary: Generate text using AI
operationId: generateText
security:
- ApiKeyAuth: []
responses:
'200':
description: Success
'429':
description: Rate limit exceeded⚠ Common Pitfalls
- •Generating OpenAPI from code instead of vice versa often leads to breaking changes without versioning alerts.
- •Missing clear error schemas makes it difficult for consumers to handle exceptions programmatically.
Implement API Key Authentication and Scoping
Avoid using standard JWTs for API products; developers prefer long-lived API keys. Use a service like Unkey or build a hashing mechanism to store keys securely. Map every key to a 'workspaceId' and a set of 'permissions' to ensure fine-grained access control.
import { verifyKey } from '@unkey/api';
async function authenticate(req: Request) {
const key = req.headers.get('Authorization')?.replace('Bearer ', '');
if (!key) return new Response('Unauthorized', { status: 401 });
const { result, error } = await verifyKey(key);
if (error || !result.valid) return new Response('Forbidden', { status: 403 });
return result.ownerId; // Use this to track usage
}⚠ Common Pitfalls
- •Storing API keys in plain text in the database.
- •Failing to provide a 'prefix' (e.g., sk_live_...) for keys, which makes it harder for secret scanners to identify leaks.
Configure Usage-Based Rate Limiting
Implement rate limiting at the edge or middleware level. Use a sliding window algorithm to prevent spikes. For API products, rate limits should be tied to the API key's tier (e.g., 100 req/min for Free, 5000 req/min for Pro).
const rateLimit = async (userId, tier) => {
const limit = tier === 'pro' ? 5000 : 100;
const current = await redis.incr(`ratelimit:${userId}`);
if (current === 1) await redis.expire(`ratelimit:${userId}`, 60);
return current <= limit;
};⚠ Common Pitfalls
- •Using a fixed window algorithm which allows double the traffic at the window boundary.
- •Not returning 'X-RateLimit-Remaining' headers to the client.
Integrate Usage Metering for Billing
For usage-based pricing, you must report consumption to your billing provider (e.g., Stripe). Instead of reporting on every request (which adds latency), aggregate usage in a background worker or use a high-performance buffer and send it in batches.
const stripe = require('stripe')(process.env.STRIPE_SECRET);
async function reportUsage(subscriptionItemId, quantity) {
await stripe.subscriptionItems.createUsageRecord(
subscriptionItemId,
{
quantity,
timestamp: Math.floor(Date.now() / 1000),
action: 'increment',
}
);
}⚠ Common Pitfalls
- •Reporting usage synchronously in the request path, significantly increasing latency.
- •Not handling Stripe API downtime; use a local queue (like BullMQ or RabbitMQ) for retries.
Automate Documentation and SDK Generation
Ensure your documentation stays in sync with your API. Use tools like Redocly or Mintlify to render your OpenAPI spec. Optionally, use Stainless or Fern to generate typed SDKs (TypeScript, Python, Go) to reduce integration friction for your users.
⚠ Common Pitfalls
- •Manual documentation updates that lag behind the actual implementation.
- •Providing code snippets that are syntactically incorrect or out of date.
What you built
Successfully launching an API product hinges on the 'Time to First Hello World'. By prioritizing a contract-first approach and automating the billing/documentation pipeline, you ensure that your API is scalable, monetizable, and developer-friendly. Monitor your '429' error rates and 'latency per endpoint' to identify when users need to upgrade or when your infrastructure needs optimization.