GICT knowledge base
API Security 101: Rate Limiting, Keys, and OAuth2 for Small Teams
Published
You built an API. People are using it. Then someone started abusing it – 50,000 requests a minute, your server is on fire, your bill is $4,000, and you do not know if they are a legitimate user or an attacker.
This is the API security story for small teams. It is not glamorous, but it is the difference between a product that survives and one that does not.
Three things protect almost every public API: rate limiting, API keys, and OAuth2. You probably need all three. None of them are hard to implement. Let us walk through when to use which, and how to do it without overengineering.
Why API Security Matters From Day One
A few patterns I have seen play out many times.
Pattern 1: The runaway client. A legitimate user writes a script with a bug. It loops, hammering your API. Your server falls over. You get paged at 3 AM.
Pattern 2: The scraper. Someone decides your data is valuable. They spin up a bot that downloads everything. Your bandwidth bill triples.
Pattern 3: The credential leak. A user accidentally commits their API key to a public GitHub repo. Bots find it within minutes and start using it.
Pattern 4: The competitor. Someone reverse-engineers your API and builds a competing product on top of it. No legal recourse if your data is “public.”
None of these are exotic attacks. All of them happen to small teams regularly. The fix for all of them is the same set of techniques.
Rate Limiting: The Foundation
Rate limiting caps how many requests a client can make in a given window. It is the single most important API protection you can add.
Without rate limiting: a single bad actor can take down your service.
With rate limiting: the same bad actor gets throttled to a reasonable rate. Everyone else is unaffected.
Common Approaches
Fixed window. “1000 requests per hour, reset on the hour.” Simple. Has edge cases at window boundaries (a client can make 2000 requests in 2 seconds if they hit the boundary), but workable.
Sliding window. Tracks request counts over the last N minutes more precisely. Better than fixed window, slightly more complex to implement.
Token bucket. Each client has a bucket with tokens. Each request consumes a token. Tokens refill at a steady rate. Smooths bursty traffic. Used by AWS, Stripe, and many large APIs.
Leaky bucket. Similar to token bucket but enforces a strict output rate. Less common, useful for protecting downstream systems.
For most small teams, fixed window or token bucket is enough. Anything more is overengineering.
How to Implement
Most API gateways and reverse proxies have rate limiting built in:
- Nginx:
limit_req_zoneandlimit_reqdirectives - Caddy: rate-limit plugins
- Cloudflare: rate limiting rules in the dashboard
- AWS API Gateway: throttle settings per stage
- Kong, Tyk, Apigee: dedicated API gateways with rich rate limiting
If you build your API in code, a Redis-based rate limiter is 50 lines of Python or Node. Most languages have libraries that handle it.
What Limits to Set
- Anonymous traffic: 60 requests per minute per IP. Tight enough to discourage abuse, loose enough for normal browsing.
- Authenticated free tier: 1000 requests per hour per API key. Adjust based on what your free tier promises.
- Paid tier: scale up with the plan. Maybe 10,000 per hour for a basic plan, 100,000 for a pro plan.
- Internal APIs: higher limits, scoped to your VPC or IP allowlist.
Start conservative. You can always loosen limits. The wrong default is “no limit” – that is what gets you paged.
API Keys: Identification, Not Authentication
API keys answer one question: “who is making this request?” They do not prove identity on their own. They identify.
A typical API key flow:
- User signs up for your service
- You generate a unique key for them (like
sk_live_abc123xyz) - User includes the key in every request (
Authorization: Bearer sk_live_abc123xyz) - You look up the key, identify the user, apply their rate limit, log the activity
Best Practices
Generate keys securely. Use cryptographically secure random. 32+ bytes of entropy. Never use sequential IDs.
Hash keys at rest. Store the SHA-256 hash, not the raw key. If your database leaks, attackers cannot use the keys directly. Show the raw key to the user once at creation, never again.
Support key rotation. Users should be able to roll keys without downtime. Support multiple active keys per account.
Scope keys. A read-only key should not be able to write. A production key should not be able to delete. Use prefixes or separate keys for different operations.
Track usage. Log every request with the key. This is your audit trail. It is also how you detect abuse.
Set expirations. Long-lived keys are a security risk. Encourage 90-day rotations for sensitive use cases.
Common Mistakes
Keys in URLs. Putting keys in query parameters leaks them in server logs and browser history. Always use headers.
Keys in client-side code. If your API key is in JavaScript on a webpage, anyone who views source can use it. Use a backend proxy for browser apps.
No rotation support. Users will get locked out if they cannot roll keys. Build rotation from the start.
Same key for everything. A single key for production and development makes it impossible to revoke one without the other.
OAuth2: When You Need to Act on Behalf of Users
API keys identify an application. OAuth2 lets your application act on behalf of a user, with their permission.
If you are building “log in with Google” or “connect your Stripe account to our app,” you are doing OAuth2.
The Flow (Simplified)
- User clicks “Connect Google” in your app
- Your app redirects them to Google’s auth page
- User approves the connection
- Google redirects back to your app with an authorization code
- Your app exchanges the code for an access token
- Your app uses the access token to call Google APIs on behalf of the user
This is OAuth2’s authorization code flow. It is the most common pattern for web apps.
When You Need OAuth2
Social login. “Log in with Google/Facebook/GitHub.” OAuth2 underneath.
Third-party integrations. When your users want to connect their accounts on other platforms to your app.
Delegated access. When your app needs to act on behalf of a user across multiple sessions without storing their password.
Enterprise SSO. When you need to integrate with corporate identity providers (Okta, Azure AD, Google Workspace).
When You Do NOT Need OAuth2
Machine-to-machine communication. Use API keys instead. Simpler.
Same-team internal APIs. Use mTLS, IP allowlists, or service tokens. OAuth2 is overkill.
Single-page apps talking to your own backend. Use a session cookie or JWT issued by your own auth system.
OAuth2 is complex because it solves a hard problem (delegated authorization across security boundaries). Do not reach for it unless you have that problem.
Practical Tips
Use a library, not a from-scratch implementation. OAuth2 has many subtle pitfalls. Libraries handle the edge cases.
Use PKCE for public clients. PKCE (Proof Key for Code Exchange) prevents authorization code interception. Required by OAuth 2.1 for public clients (mobile apps, SPAs).
Store tokens securely. Access tokens should be encrypted at rest. Refresh tokens especially – they are long-lived and powerful.
Handle token refresh. Access tokens expire (typically 1 hour). Refresh tokens let you get new ones without bothering the user. Build this from day one.
Revocation matters. Build a way to revoke tokens. Users will demand it the first time they think their account is compromised.
Other Things You Actually Need
Rate limiting, API keys, and OAuth2 are the foundation. A few other things matter.
HTTPS everywhere. Never accept API requests over plain HTTP. Use TLS 1.2 or higher. Free with Let’s Encrypt.
Input validation. Validate everything. Length, format, range, type. Reject malformed requests early.
CORS configuration. If your API is called from browsers, configure Cross-Origin Resource Sharing carefully. Do not use Access-Control-Allow-Origin: * for authenticated endpoints.
Logging. Log every request with timestamp, key, endpoint, response code, latency. This is your debugging tool and your abuse detector.
Monitoring. Alert on sudden spikes in error rates, response times, or unusual traffic patterns.
Webhook signatures. If you send webhooks, sign them (HMAC-SHA256 is standard). Verify the signature on the receiver side.
None of these are hard. All of them are commonly skipped. Skip them and you will regret it.
What to Build First
If you have limited time, prioritize like this.
Day 1 (before launch): HTTPS, basic rate limiting, API keys for identification.
Week 1: Logging, monitoring, abuse detection.
Month 1: Key rotation, scoped keys, audit logs.
Quarter 1: OAuth2 if you need third-party integrations. Webhook signatures if you send webhooks.
This is roughly the order of importance. Most APIs that get breached skipped steps in this list. The ones that did not have problems usually built them in this order.
What You Can Do Today
You do not need a security overhaul. Start here.
Step 1: Turn on rate limiting. Cloudflare, Nginx, or your API gateway. 60 req/min per IP is a reasonable starting point.
Step 2: Make sure all keys are hashed at rest. If you store raw keys in your database, fix that today.
Step 3: Add HTTPS. Let’s Encrypt is free. Renewals are automatic. There is no excuse for plain HTTP in 2026.
Step 4: Set up logging. Every request, with key, endpoint, response code. You cannot detect abuse without it.
Step 5: Read your traffic. Look at your top users by request volume. Are they legitimate? Are they hitting limits? Are they doing anything weird?
If you host your API on InterServer infrastructure, their VPS and dedicated server lines support any of the patterns described above. Rate limiting on Nginx, Caddy, or Cloudflare in front of your origin works the same whether your API is hosted there or anywhere else.
Conclusion
API security is not glamorous but it is essential. The good news: the basics cover most of what you need. Rate limiting, API keys, HTTPS, logging. Build those first. Add OAuth2 only when you actually need third-party integrations.
The teams that get this wrong skip the basics. The teams that get it right do the boring work up front and save themselves from 3 AM pages later.
Start with rate limiting today. Add key hashing tomorrow. Build OAuth2 when you need it. That is the order. Anything more complicated is overengineering for a small team.
The attackers are not sophisticated. They are running scripts that look for the easy targets. The basics make you a hard target. That is enough.
Related guides
How to Switch Hosting Providers With Zero Downtime
How to switch hosting providers with zero downtime in 2026: the migration plan, DNS cutover strategy, data sync strategies, rollback procedure, and a pre-migration...
Hosting GuidesDatabase Sharding Strategies for Growing Apps
Database sharding strategies for growing apps in 2026: when you actually need sharding, how to choose a shard key, the 5 main strategies, cross-shard query costs...
Hosting GuidesVector Databases Explained: pgvector for AI-Powered Apps
Vector databases explained for AI apps in 2026: what they are, why pgvector is the right default for most teams, how embeddings work, chunking strategies, production...
Hosting GuidesWhat Good Hosting Support Actually Looks Like
What good hosting support looks like in 2026: quick first response, real engineers, public status pages, transparent postmortems, and how to test support quality before...
