The moment your SaaS lands a second paying customer, someone will ask: "Can I connect this to my workflow?" Most founders say "it's on the roadmap" and move on. That works once. The fifth time you hear it, you are watching churn build.
A customer-facing API is not as big a lift as it sounds -- especially if you already have route handlers, API key storage, and rate limiting in your app. The gap is mostly confidence and documentation.
Your internal API already exists. Every /api/ route your frontend calls is an API. The difference with a customer-facing API is three things:
Everything else -- validation, error handling, rate limiting -- you are already doing if you started from a Next.js SaaS boilerplate.
The API key management post covers the storage pattern: generate with crypto.randomBytes, show the key once, store only the SHA-256 hash. For a customer-facing key, extend the keys table with two extra columns: name (so customers can label their keys, e.g. "Production" vs "Staging") and lastUsedAt (so the dashboard can show whether a key is still active).
A customer creates a key from their account settings. You show it once -- copy it or lose it. Every API request sends it as a Bearer token:
// Route handler: check API key before JWT
const authHeader = request.headers.get('authorization');
if (authHeader?.startsWith('Bearer sk_')) {
const hash = sha256(authHeader.slice(7));
const apiKey = await apiKeyRepo.findByHash(hash);
if (!apiKey) throw new HttpError(401, 'Invalid API key');
return handleWithUser(apiKey.userId, request);
}
The boilerplate's getUserFromRequest already handles JWT. Add a parallel getApiKeyUser that returns the same shape so route handlers stay thin regardless of which auth method the caller uses.
The simplest versioning that scales is a URL prefix: /api/v1/posts, /api/v2/posts. Route handlers under app/api/v1/ and app/api/v2/ are independent files -- you can change v2 without touching v1.
When do you actually need v2? Only when a breaking change is unavoidable:
Adding a new optional field is never a breaking change. Deprecating a field -- keeping it in the response, marking it in docs, removing it in the next major version -- gives integrators a migration window. For most early-stage products, v1 runs for a long time. Invest in a clean error shape now and you can stand behind it.
The rate limiting pattern applies directly. Add a tier column to your keys table and map each tier to a request-per-minute ceiling:
| Tier | Limit |
|---|---|
| free | 60 req/min |
| pro | 600 req/min |
| custom | negotiated |
Return the limit and remaining count in response headers so customers can write backoff logic without guessing:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 43
X-RateLimit-Reset: 1722873600
Those three headers prevent a category of support tickets entirely. When a customer's script gets a 429, they know exactly how long to wait.
You do not need Swagger or a dedicated docs platform on day one. A single MDX page at /docs/api with three sections unblocks most integrators:
Keep docs in your codebase, not in a separate tool. Docs that live next to the routes get updated when the routes change. Docs in a third-party portal drift within months.
Opening an API to customers is a product commitment. Every field you expose is a field you cannot quietly remove. Every status code is a contract.
That sounds scary, but the practical floor is low: start with read-only endpoints for the resources your customers most want to query. Export their own data first. Add write endpoints when the demand is clear. A narrow, stable v1 is worth more than a wide, fragile one.
Your Next.js SaaS already has the raw material: route handlers, Drizzle ORM for key storage, rate limiting middleware, and a service layer that keeps business logic out of routes. The work is:
name and lastUsedAtgetApiKeyUser auth branch alongside getUserFromRequest/api/v1//docs/api MDX page with auth, endpoints, and error codesWhen the next customer asks "do you have an API?", you send them a link. They are integrated by the end of the day.
Get the Next.js SaaS boilerplate and ship your public API this week.