Most teams build a “public API” the way they build internal services: they expose a load balancer, forward traffic to the app, and hope the app can survive the internet.
Then the first real incident hits. Not a security breach. Something dumber and more expensive: your upstream starts throwing 500s because a botnet or a misbehaving partner hammered one endpoint at 50x your expected rate. You end up rate-limiting in the app after the damage is done, and you still have logs full of leaked credentials because someone put API keys in query parameters “just for testing.”
The uncomfortable truth: if your edge does not enforce identity and volume limits, your backends will become your security boundary. They should not be.
The secure request path you actually want
You need one choke point. The public entry is the gateway. Everything else stays private. Your gateway does three jobs before the request touches any internal service:
First, it authenticates the caller using an API key and rejects unknown keys.
Second, it authorizes what that key is allowed to do. Even if you start simple, you must design for “key maps to permissions,” not “one key equals full access.”
Third, it enforces rate limits per route and method so availability survives abuse and mistakes.
Only after those checks does it forward to a private upstream over a private network path.
Most teams get this wrong in one specific way: they key rate limiting by IP only. That feels safe until you have NAT, mobile networks, corporate proxies, or partner gateways. Then one shared egress IP becomes the “one true limiter key,” and you throttle paying customers because one tenant got noisy.
Here’s the end-to-end flow you should implement:
- Client calls your public API over TLS.
- Gateway matches route and selects the upstream.
- Gateway extracts API key from a header, validates it, and attaches identity context.
- Gateway applies the correct rate limit policy and returns 429 Too Many Requests if exceeded.
- Gateway forwards the request to the private upstream.
- Gateway returns the response and records audit logs and metrics.
This is the difference between “we protected the app” and “we protected the system.”

Shows the gateway enforcing API key auth and rate limiting before forwarding to private services.
API keys: simple auth, but treat them like secrets
API keys are not OAuth. You are not building user identity. You are building service identity and developer access. That simplicity is the point.
But the operational failure modes are real, and you should design around them:
API keys must never live in query strings. URLs get logged, cached, forwarded, and scraped. Use a header like x-api-key or Authorization: ApiKey <key> and standardize it across all clients.
Store keys in a place you can rotate without redeploying the whole gateway. For enterprise setups, you usually want a central registry and a process for revocation. If you store credentials in config files, you will eventually leak them through a bad build artifact or a support ticket.
Design for key lifecycle from day one:
- rotation with overlap (old key still works for a short window)
- revocation (immediate deny)
- auditability (you can answer “who called what”)
- least privilege (a key should not automatically get access to every route)
Authorization mapping is where teams either stay sane or create a mess. If you only implement allow/deny for now, you can still structure it as “key -> permissions -> route filters.” That keeps you from rewriting everything when you add tiers like sandbox vs production or free vs premium.
Also, do not assume “one key per tenant” will stay correct. Tenants share keys, developers reuse keys, and partners demand “temporary access.” Build your model so you can add more keys, not so you can only rotate one.
Rate limits: enforce cost, not just request counts
Rate limiting is not a vibe. It is a budget. Your gateway should enforce budgets that match your backend cost profile.
Start by deciding the limiter key. The most reliable approach for public APIs is per API key, optionally combined with route and method. Use IP as a secondary signal, not the primary identity.
Then decide the policy shape. Token bucket is usually the best default because it allows short bursts while enforcing a long-term rate. Fixed windows are easy to reason about, but they create edge-case spikes at window boundaries that can still knock over expensive endpoints.
Now pick routes carefully. Not all endpoints cost the same. A /v1/search call might be 10x more expensive than /v1/status. If you apply one global limit, you either throttle too hard or you let the expensive endpoint burn your resources.
When you exceed limits, respond consistently:
- Use 429 Too Many Requests
- Include a Retry-After header so clients back off instead of retrying instantly
- Emit rate limit metrics so you can see which key and which route are causing pain
Here’s a concrete number to anchor your planning: many public APIs start with something like 60 requests per minute per API key per endpoint for “read-ish” operations, then lower or raise based on cost. You will tune it, but you need a baseline that prevents uncontrolled load while you learn.
Most teams get this wrong by rate limiting only at the app layer and only on “global requests.” That misses the real cost drivers and makes incident response slow. The gateway should do the cheap checks and reject early.
Private upstreams: make the gateway your only exposure
Private upstreams are how you stop attackers from learning your internal topology.
If your internal services are reachable from the public internet, you lose. Even if you have auth, you increase your attack surface and give attackers more ways to find misconfigurations.
So your gateway should be the only component with public reachability. Upstreams should be reachable over a private network path. That can be internal hostnames, private load balancers, or internal service discovery, but the key requirement stays the same: the internet cannot directly connect to them.
This also improves reliability. You can do graceful failure at the edge: return clean 4xx errors for auth and rate limiting, and only forward to backends when you know the request is allowed.
Another mistake to avoid: mixing routing logic with application logic. Route selection and policy enforcement belong at the edge. The app should not need to understand whether the caller came from the public internet or an internal service.
In practice, you configure your gateway with upstream targets per route. For example:
- /v1/orders/* routes to an internal orders service
- /v1/payments/* routes to an internal payments service
- health endpoints route to a lightweight internal handler
If you ever need to change upstreams, you should do it in the gateway config, not by changing the application.
What to configure in IBEE (and how to keep it maintainable)
You can implement the secure path using IBEE as your public edge, with gateway-like routing and policy enforcement in front of private services. Keep the configuration disciplined so you do not turn your gateway into a second application.
A maintainable setup usually looks like this:
You define a small set of route patterns that map to internal upstreams. You do not use a catch-all that forwards everything to one backend. That creates “policy gaps” when you add new endpoints.
You enable API key verification at the edge. Standardize the header name. Reject missing keys immediately. Reject unknown keys with a consistent 401 response.
You attach rate limits to route and method. You also decide the limiter key. Use API key based limits first. If you need additional protection against key sharing, you can add IP as a secondary dimension, but do it intentionally.
You forward only to private upstreams. If a route targets an internal service, ensure it is not publicly reachable by any other path. Your network design should make the gateway the only ingress.
Finally, you log and measure. You need audit logs that can answer:
- which key called which route
- whether requests were rejected by auth or by rate limiting
- whether the upstream was ever reached
That is how you debug incidents without guessing. Your app logs are not enough.
If you also store secrets needed by your gateway or clients, use Secret Manager so you do not bake credentials into configs or CI logs. Rotation becomes a process, not a fire drill.
One concrete action you can take today
Pick one endpoint that is public and expensive, like /v1/search. Put it behind the full secure path now:
- Require an API key header and reject missing or unknown keys.
- Apply a per-API-key rate limit for that route and method, with a 429 response and Retry-After.
- Route it to a private upstream only, with no public exposure to the backend.
Do this for one endpoint first. Once it works end-to-end, you can replicate the pattern across the rest of your API without rewriting your security model.







