04 · Designing Public Developer-Facing APIs¶
Building an API for your own frontend is one job. Building one that thousands of third-party developers integrate against, without ever talking to your team, is a different discipline: stability, discoverability, and self-service are the product.
API keys and developer accounts¶
Unlike an internal OAuth2 token tied to a logged-in user session, a public API typically issues long-lived API keys per developer account, scoped and revocable independently:
curl -X POST https://api.example.com/v1/api-keys \
-H "Authorization: Bearer $DASHBOARD_SESSION_TOKEN" \
-d '{"name": "production-server", "scopes": ["orders:read"]}'
{ "id": "key_9", "key": "sk_live_51H8x...", "scopes": ["orders:read"], "created_at": "2026-08-31T00:00:00Z" }
The raw key is shown exactly once at creation and never again — only its prefix and metadata are retrievable afterward, so a leaked key can be identified and revoked without the API ever storing the plaintext.
Sandbox vs. live environments¶
curl https://api.example.com/v1/orders -H "Authorization: Bearer sk_test_..."
curl https://api.example.com/v1/orders -H "Authorization: Bearer sk_live_..."
A sk_test_ key routes to a fully isolated sandbox — fake payment
processing, no real charges, resettable data — so a developer building
an integration can experiment freely and safely before ever touching
production. Key prefix signals environment, catching the common
mistake of a test key accidentally shipped to production (which simply
fails auth against live) instead of silently doing something dangerous.
SDKs generated from the OpenAPI spec¶
import example_api
client = example_api.Client(api_key="sk_live_...")
order = client.orders.create(item_id=5, qty=2)
Auto-generating official SDKs in major languages from the same OpenAPI spec that drives the reference docs (Level 3, module 7) keeps every language binding in sync automatically when the spec changes — instead of hand-maintained SDKs drifting out of date.
Predictable, generous rate limits — and telling developers about them¶
Third-party developers build automated systems against these headers — unlike an internal team you can just tell "don't hammer that endpoint," a public integrator needs the limit itself, machine-readable, to build correct backoff logic.
API stability as a product commitment¶
Public APIs need a much longer deprecation runway than internal ones (Level 3, module 6) — a third-party developer might not read your changelog for months. A common commitment:
## Stability policy
- v1 will be supported for a minimum of 24 months after v2 ships.
- Breaking changes are only ever shipped in a new major version, never
as a patch to an existing one.
- New optional fields may be added to v1 responses at any time.
Worked example: a developer's first hour with your API¶
- They sign up, land on a dashboard, and generate a sandbox key with zero human involvement.
- The quickstart guide (Level 3, module 7) gets them to one successful
curlcall in under 5 minutes. - They install the official SDK (
pip install example-api) and build a small integration against sandbox data. - They hit a rate limit while testing in a loop, read
X-RateLimit-Resetin the response, and back off correctly without filing a support ticket. - They flip to a live key, ship to production, confident nothing will silently break because of the published stability policy.
Every step above is self-service — no human on your team was involved, which is the entire point of a public developer API's design.
How It Actually Works¶
Running a public developer API means handling identity and quota enforcement for callers you don't control, at every single request — mechanically different from an internal API where you trust the caller.
API key issuance and lookup: a key like sk_live_a1b2c3... is
typically not stored in your database in plaintext — it's hashed (like a
password) on issuance, and every incoming request re-hashes the presented
key and looks up the hash, so a database leak doesn't hand attackers
usable keys directly (mirroring password storage practice, module 7,
Level 1 auth basics extended). The key's prefix (sk_live_ vs sk_test_)
is often left unhashed specifically so it can be pattern-matched and
revoked in bulk, or displayed truncated in a dashboard, without exposing
the secret portion.
Per-key rate limiting reuses the token-bucket mechanism (module 6,
Level 2) but keyed by API key rather than IP or user session — this is
why a public API's 429 responses typically include X-RateLimit-Limit,
X-RateLimit-Remaining, and X-RateLimit-Reset headers computed directly
off that same bucket's current token count and refill schedule, giving
well-behaved SDKs enough information to self-throttle before hitting the
wall.
SDK generation from your OpenAPI spec (module 4, Level 2) means a client library in Python, JS, or Go is mechanically produced by walking the same schema tree a doc generator uses — a breaking schema change doesn't just break hand-written client code, it breaks the next SDK build for every language you publish, which is why public APIs are held to stricter backward-compatibility discipline (module 6, Level 3) than internal ones: SDK consumers can't just "read the updated docs and adapt" as fluidly as an internal team sitting next to yours.
Exercise¶
- Why show a raw API key only once at creation, rather than letting a developer view it again later from the dashboard?
- Explain why a sandbox environment needs to be genuinely isolated (separate data, no real side effects) rather than just a flag on production data.
- Design the
scopesa public payments API might offer beyondorders:read— think about what a developer building a read-only analytics dashboard should be limited to versus one processing refunds. - Why does a public API need a longer deprecation runway than an internal one used only by your own frontend team?