Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

How to Build a Customer-Facing API for Your Next.js SaaS -- API Keys, Rate Limits, and Docs

August 5, 2026
nextjssaasauthdrizzle-ormboilerplate

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.

What separates an internal API from a customer-facing one

Your internal API already exists. Every /api/ route your frontend calls is an API. The difference with a customer-facing API is three things:

  • Authentication -- your UI sends a JWT; your customers send an API key
  • Versioning -- you need to avoid breaking integrations when you change a response shape
  • Discoverability -- customers need docs they can read without emailing you

Everything else -- validation, error handling, rate limiting -- you are already doing if you started from a Next.js SaaS boilerplate.

Issuing API keys to customers

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.

Versioning without pain

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:

  • Removing a field a customer might be reading
  • Renaming or changing the type of an existing field
  • Changing what an HTTP status code means

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.

Rate limiting by API key tier

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.

A docs page customers can actually use

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:

  1. Authentication -- how to create a key, how to send it, what a 401 means
  2. Endpoints -- one table per resource: method, path, required fields, example response
  3. Error reference -- your status codes and what each one means in your product's terms (not just HTTP boilerplate)

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.

The decision you are actually making

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.

Putting it together

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:

  1. Extend the API keys table with name and lastUsedAt
  2. Add a getApiKeyUser auth branch alongside getUserFromRequest
  3. Put customer-facing routes under /api/v1/
  4. Add rate limit headers to every response
  5. Write a /docs/api MDX page with auth, endpoints, and error codes

When 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.