WORK IN PROGRESS.
WILL TAKE 3 MONTHS TO COMPLETE
TARGET COMPLETION DATE - Dec 2026
I've been trying to write this handbook for a year now. Today (2026-09-18) I finally decided to start publishing it. Based on my rough notes so far, this is going to be a rather large article.
This note exists for:
- Teaching newcomers or my junior teammates the components things that go into an API's design
- Helping me remember stuff
- Being a standards guide for feeding LLMs (before they starts auditing existing APIs or creating new ones).
This note is for you if you're - writing a web framework, building an API gateway, developing an API, consuming an API.
Over the years, I've learnt to judge the quality of an org's engineering through:
- Clarity of API endpoints
- Accuracy of HTTP response codes
- Details in Response Headers
This note is supposed to help in these 3 areas.
Rate limiting
When rate limit is exceeded, return a HTTP 429 - Too many requests.
And to inform consumers about the rate limit status, 3 headers are are used:
RateLimit-PolicyRateLimitRetry-After
Do not impose rate limits without clearly informing consumers about what quotas have been imposed, and how much of it has been used.
Headers to use
RateLimit-Policy
Tells the rate limit policies that are being applied.
EXAMPLE
Suppose there are 2 policies:
- daily policy allows 10,000 requests per day
- burst policy allows 100 requests per minute
The value of RateLimit-Policy will be:
RateLimit-Policy: "burst";q=100;t=60, "daily";q=10000;t=86400;pk=oBJER4zNn2XZERtPXTBh3YzRNp707L6u
Identifiers and their meanings:
- q - quota of the policy
- qu - unit that the quota is measured in (values could be -
requests,content-bytes,concurrent-requests) - w - time window of the quota
- pk - (optional) - partition key used to scope usage (could be API key, account, ip address etc)
RateLimit
This is my favourite header!
Tells the current quota remaining in each policy.
Suppose the default rate limit policy that allows 100 requests in a 60 second window. It will have a header value:
RateLimit: "default";r=100;t=30
Identifiers and their meanings:
- r - API hits remaining in policy (+ve integer).
0means all the API hits of the quota have been used up. - t - time window window length in seconds, in which the quota can be used
- pk - (optional) - partition key used to scope usage (could be API key, account, ip address etc)
RateLimit can return the limits of multiple policies at once. Separate each policy by comma, and order the policies by time-window length in ascending order.
EXAMPLE
Consider 2 policies
- daily policy allows 10,000 requests per day
- burst policy allows 100 requests per minute
- The usage is measured against an API key
Suppose 10 requests have been used up in a minute. The returned header will have value:
RateLimit: "burst";r=90;t=60, "daily";r=9990;t=86400;pk=oBJER4zNn2XZERtPXTBh3YzRNp707L6u
These 2 headers are enough, and I recommend using these 2 only.
Retry-After
Tells how long the client needs to wait before retrying the API request. Its value is in seconds.
There are 2 ways of reporting this value:
(1) Delta-seconds
Retry-After: 60
(2) HTTP-date
Gives a date in format Day, DD Mon YYYY HH:MM:SS GMT.
Retry-After: Sat, 18 Sep 2026 01:00:00 GMT
Note on headers
To report usage, you'll often find existing using 3 headers RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. That too is fine, but the 2 header combo defined above is simply superior. (RateLimit-Limit & RateLimit-Remaining got combined into a single header - RateLimit)
Rate limiting algorithms to use - WORK IN PROGRESS
This is a very well known problem area, and infra components like redis, language-specific libraries etc have the building blocks required to implement each of them.
A list of algos for controlling API requests within a time window are:
- Token bucket - good algo to default to
- Fixed window counter - very stupid algo, inexpensive to implement at scale, will lead to 'bursty' usage
- Sliding window log - very expensive (cache wise) to implement
- Sliding window counter - inexpensive (I personally like this one the most)
- Leaky bucket - leads to very smooth processing, perfect for pull based architectures
There's also a concurrency limiting algo.
Conveying scheduled downtime
Suppose the API is currently down for planned maintenance. In this case:
- Return HTTP 503 - Service Unavailable
- Set response header
Retry-After
Retry-After header tells how long the client needs to wait before retrying the API request. Its value is in seconds.
Can return value in 2 formats:
(1) HTTP-date (recommended, easier to use) (uses format Day, DD Mon YYYY HH:MM:SS GMT)
Retry-After: Sat, 18 Sep 2026 01:00:00 GMT
(2) Delta-seconds - tells time to wait in seconds
Retry-After: 3600
This is extremely useful for scrapers, because this clearly tells them when to resume operation, thus removing the need for polling the API to discover when it comes back.
Resources:
- http.dev/headers - EVERYTHING that I know about headers has come from here